@dotcms/analytics 1.2.0-next.4 → 1.2.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/lib/core/dot-content-analytics.js +31 -21
- package/lib/core/plugin/dot-analytics.plugin.d.ts +21 -9
- package/lib/core/plugin/dot-analytics.plugin.js +79 -24
- package/lib/core/plugin/enricher/dot-analytics.enricher.plugin.d.ts +14 -10
- package/lib/core/plugin/enricher/dot-analytics.enricher.plugin.js +26 -38
- package/lib/core/plugin/identity/dot-analytics.identity.plugin.d.ts +2 -20
- package/lib/core/plugin/impression/dot-analytics.impression.plugin.d.ts +38 -0
- package/lib/core/plugin/impression/dot-analytics.impression.plugin.js +35 -0
- package/lib/core/plugin/impression/dot-analytics.impression.utils.d.ts +51 -0
- package/lib/core/plugin/impression/dot-analytics.impression.utils.js +50 -0
- package/lib/core/plugin/impression/index.d.ts +2 -0
- package/lib/core/shared/constants/dot-content-analytics.constants.d.ts +46 -0
- package/lib/core/shared/constants/dot-content-analytics.constants.js +30 -16
- package/lib/core/shared/dot-content-analytics.http.d.ts +2 -2
- package/lib/core/shared/dot-content-analytics.impression-tracker.d.ts +62 -0
- package/lib/core/shared/dot-content-analytics.impression-tracker.js +218 -0
- package/lib/core/shared/dot-content-analytics.utils.d.ts +20 -0
- package/lib/core/shared/models/data.model.d.ts +39 -1
- package/lib/core/shared/models/event.model.d.ts +40 -2
- package/lib/core/shared/models/library.model.d.ts +60 -25
- package/lib/core/shared/models/request.model.d.ts +17 -9
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ export declare const ANALYTICS_ENDPOINT = "/api/v1/analytics/content/event";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare const DotCMSPredefinedEventType: {
|
|
9
9
|
readonly PAGEVIEW: "pageview";
|
|
10
|
+
readonly CONTENT_IMPRESSION: "content_impression";
|
|
10
11
|
};
|
|
11
12
|
/**
|
|
12
13
|
* Type for structured events
|
|
@@ -68,3 +69,48 @@ export declare const ANALYTICS_MINIFIED_SCRIPT_NAME = "ca.min.js";
|
|
|
68
69
|
* These should be filtered out to only keep user-provided properties
|
|
69
70
|
*/
|
|
70
71
|
export declare const ANALYTICS_JS_DEFAULT_PROPERTIES: readonly ["title", "url", "path", "hash", "search", "width", "height", "referrer"];
|
|
72
|
+
/**
|
|
73
|
+
* Impression tracking configuration constants
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* Default minimum percentage of element that must be visible (0.0 to 1.0)
|
|
77
|
+
*/
|
|
78
|
+
export declare const DEFAULT_IMPRESSION_VISIBILITY_THRESHOLD = 0.5;
|
|
79
|
+
/**
|
|
80
|
+
* Default minimum time in milliseconds element must be visible
|
|
81
|
+
*/
|
|
82
|
+
export declare const DEFAULT_IMPRESSION_DWELL_MS = 750;
|
|
83
|
+
/**
|
|
84
|
+
* Default maximum number of elements to track (performance limit)
|
|
85
|
+
*/
|
|
86
|
+
export declare const DEFAULT_IMPRESSION_MAX_NODES = 100;
|
|
87
|
+
/**
|
|
88
|
+
* Default throttle time in milliseconds for intersection callbacks
|
|
89
|
+
*/
|
|
90
|
+
export declare const DEFAULT_IMPRESSION_THROTTLE_MS = 100;
|
|
91
|
+
/**
|
|
92
|
+
* Default debounce time in milliseconds for MutationObserver
|
|
93
|
+
*/
|
|
94
|
+
export declare const DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS = 250;
|
|
95
|
+
/**
|
|
96
|
+
* Default impression tracking configuration
|
|
97
|
+
*/
|
|
98
|
+
export declare const DEFAULT_IMPRESSION_CONFIG: {
|
|
99
|
+
readonly visibilityThreshold: 0.5;
|
|
100
|
+
readonly dwellMs: 750;
|
|
101
|
+
readonly maxNodes: 100;
|
|
102
|
+
readonly throttleMs: 100;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Event type for content impressions
|
|
106
|
+
* Must match DotCMSPredefinedEventType.CONTENT_IMPRESSION
|
|
107
|
+
*/
|
|
108
|
+
export declare const IMPRESSION_EVENT_TYPE = "content_impression";
|
|
109
|
+
/**
|
|
110
|
+
* Session storage key for tracked impressions (deduplication)
|
|
111
|
+
*/
|
|
112
|
+
export declare const IMPRESSION_SESSION_KEY = "dot_analytics_impressions";
|
|
113
|
+
/**
|
|
114
|
+
* CSS class selector for trackable contentlets
|
|
115
|
+
*/
|
|
116
|
+
export declare const ANALYTICS_CONTENTLET_CLASS = "dotcms-analytics-contentlet";
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
const
|
|
2
|
-
PAGEVIEW: "pageview"
|
|
3
|
-
|
|
1
|
+
const s = "/api/v1/analytics/content/event", I = {
|
|
2
|
+
PAGEVIEW: "pageview",
|
|
3
|
+
CONTENT_IMPRESSION: "content_impression"
|
|
4
|
+
}, e = [
|
|
4
5
|
"utm_source",
|
|
5
6
|
"utm_medium",
|
|
6
7
|
"utm_campaign",
|
|
7
8
|
"utm_term",
|
|
8
9
|
"utm_content"
|
|
9
|
-
],
|
|
10
|
-
eventBatchSize:
|
|
10
|
+
], o = 30, c = "dot_analytics_session_id", N = "dot_analytics_user_id", E = 15, _ = 5e3, O = ["click"], i = {
|
|
11
|
+
eventBatchSize: E,
|
|
11
12
|
// Max events per batch - auto-sends when reached
|
|
12
13
|
flushInterval: _
|
|
13
14
|
// Time between flushes - sends whatever is queued
|
|
14
|
-
},
|
|
15
|
+
}, A = [
|
|
15
16
|
"title",
|
|
16
17
|
"url",
|
|
17
18
|
"path",
|
|
@@ -20,15 +21,28 @@ const E = "/api/v1/analytics/content/event", e = {
|
|
|
20
21
|
"width",
|
|
21
22
|
"height",
|
|
22
23
|
"referrer"
|
|
23
|
-
]
|
|
24
|
+
], t = 0.5, S = 750, T = 100, n = 100, U = 250, L = {
|
|
25
|
+
visibilityThreshold: t,
|
|
26
|
+
dwellMs: S,
|
|
27
|
+
maxNodes: T,
|
|
28
|
+
throttleMs: n
|
|
29
|
+
}, D = "content_impression", M = "dotcms-analytics-contentlet";
|
|
24
30
|
export {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
O as ACTIVITY_EVENTS,
|
|
32
|
+
M as ANALYTICS_CONTENTLET_CLASS,
|
|
33
|
+
s as ANALYTICS_ENDPOINT,
|
|
34
|
+
A as ANALYTICS_JS_DEFAULT_PROPERTIES,
|
|
35
|
+
L as DEFAULT_IMPRESSION_CONFIG,
|
|
36
|
+
S as DEFAULT_IMPRESSION_DWELL_MS,
|
|
37
|
+
T as DEFAULT_IMPRESSION_MAX_NODES,
|
|
38
|
+
U as DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS,
|
|
39
|
+
n as DEFAULT_IMPRESSION_THROTTLE_MS,
|
|
40
|
+
t as DEFAULT_IMPRESSION_VISIBILITY_THRESHOLD,
|
|
41
|
+
i as DEFAULT_QUEUE_CONFIG,
|
|
42
|
+
o as DEFAULT_SESSION_TIMEOUT_MINUTES,
|
|
43
|
+
I as DotCMSPredefinedEventType,
|
|
44
|
+
e as EXPECTED_UTM_KEYS,
|
|
45
|
+
D as IMPRESSION_EVENT_TYPE,
|
|
46
|
+
c as SESSION_STORAGE_KEY,
|
|
47
|
+
N as USER_ID_KEY
|
|
34
48
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DotCMSAnalyticsConfig,
|
|
1
|
+
import { DotCMSAnalyticsConfig, DotCMSAnalyticsRequestBody } from './models';
|
|
2
2
|
/**
|
|
3
3
|
* Send analytics events to the server using fetch API
|
|
4
4
|
* @param payload - The event payload data
|
|
@@ -6,4 +6,4 @@ import { DotCMSAnalyticsConfig, DotCMSEvent, DotCMSRequestBody } 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:
|
|
9
|
+
export declare const sendAnalyticsEvent: (payload: DotCMSAnalyticsRequestBody, config: DotCMSAnalyticsConfig, keepalive?: boolean) => Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DotCMSAnalyticsConfig, DotCMSContentImpressionPayload } from './models';
|
|
2
|
+
/** Callback function for impression events */
|
|
3
|
+
export type ImpressionCallback = (eventName: string, payload: DotCMSContentImpressionPayload) => void;
|
|
4
|
+
/** Subscription object with unsubscribe method */
|
|
5
|
+
export interface ImpressionSubscription {
|
|
6
|
+
unsubscribe: () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Tracks content impressions using IntersectionObserver and dwell time.
|
|
10
|
+
* Emits events through subscriptions when impressions are detected.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DotCMSImpressionTracker {
|
|
13
|
+
private observer;
|
|
14
|
+
private mutationObserver;
|
|
15
|
+
private elementImpressionStates;
|
|
16
|
+
private sessionTrackedImpressions;
|
|
17
|
+
private impressionConfig;
|
|
18
|
+
private currentPagePath;
|
|
19
|
+
private subscribers;
|
|
20
|
+
private debug;
|
|
21
|
+
constructor(config: DotCMSAnalyticsConfig);
|
|
22
|
+
/**
|
|
23
|
+
* Subscribe to impression events
|
|
24
|
+
* @param callback - Function called when impression is detected
|
|
25
|
+
* @returns Subscription object with unsubscribe method
|
|
26
|
+
*/
|
|
27
|
+
onImpression(callback: ImpressionCallback): ImpressionSubscription;
|
|
28
|
+
/** Notifies all subscribers of an impression */
|
|
29
|
+
private notifySubscribers;
|
|
30
|
+
/** Merges user config with defaults */
|
|
31
|
+
private resolveImpressionConfig;
|
|
32
|
+
/** Initializes tracking: sets up observers, finds contentlets, handles visibility/navigation */
|
|
33
|
+
initialize(): void;
|
|
34
|
+
/** Sets up IntersectionObserver with configured visibility threshold */
|
|
35
|
+
private initializeIntersectionObserver;
|
|
36
|
+
/** Finds contentlets in DOM, validates them, and starts observing (respects maxNodes limit) */
|
|
37
|
+
private findAndObserveContentletElements;
|
|
38
|
+
/** Watches for new contentlets added to DOM (debounced for performance) */
|
|
39
|
+
private initializeDynamicContentDetector;
|
|
40
|
+
/** Cancels all timers when page is hidden (prevents false impressions) */
|
|
41
|
+
private initializePageVisibilityHandler;
|
|
42
|
+
/** Resets tracking on SPA navigation (listens to pushState, replaceState, popstate) */
|
|
43
|
+
private initializePageNavigationHandler;
|
|
44
|
+
/** Handles visibility changes: starts timer on enter, cancels on exit */
|
|
45
|
+
private processIntersectionChanges;
|
|
46
|
+
/** Starts dwell timer; fires impression if element still visible when timer expires */
|
|
47
|
+
private startImpressionDwellTimer;
|
|
48
|
+
/** Cancels active dwell timer (element left viewport before dwell time) */
|
|
49
|
+
private cancelImpressionDwellTimer;
|
|
50
|
+
/** Fires impression event with content & position data (page data added by enricher plugin) */
|
|
51
|
+
private trackAndSendImpression;
|
|
52
|
+
/** Returns skip reason if element is hidden/too small, null if trackable */
|
|
53
|
+
private shouldSkipElement;
|
|
54
|
+
/** Post-dwell check: verifies element still meets visibility threshold */
|
|
55
|
+
private isElementStillVisible;
|
|
56
|
+
/** Checks if impression already fired in current page session */
|
|
57
|
+
private hasBeenTrackedInSession;
|
|
58
|
+
/** Marks impression as tracked (prevents duplicates in same page session) */
|
|
59
|
+
private markImpressionAsTracked;
|
|
60
|
+
/** Cleanup: disconnects observers, clears timers and state */
|
|
61
|
+
cleanup(): void;
|
|
62
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { getUVEState as d } from "../../../uve/src/lib/core/core.utils.js";
|
|
2
|
+
import "../../../uve/src/internal/constants.js";
|
|
3
|
+
import { DEFAULT_IMPRESSION_CONFIG as l, ANALYTICS_CONTENTLET_CLASS as a, IMPRESSION_EVENT_TYPE as h, DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS as u } from "./constants/dot-content-analytics.constants.js";
|
|
4
|
+
import { extractContentletIdentifier as c, createDebounce as m, extractContentletData as p, getViewportMetrics as b, isElementMeetingVisibilityThreshold as f } from "../plugin/impression/dot-analytics.impression.utils.js";
|
|
5
|
+
class I {
|
|
6
|
+
constructor(e) {
|
|
7
|
+
this.observer = null, this.mutationObserver = null, this.elementImpressionStates = /* @__PURE__ */ new Map(), this.sessionTrackedImpressions = /* @__PURE__ */ new Set(), this.currentPagePath = "", this.subscribers = /* @__PURE__ */ new Set(), this.debug = e.debug ?? !1, this.impressionConfig = this.resolveImpressionConfig(e.impressions);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe to impression events
|
|
11
|
+
* @param callback - Function called when impression is detected
|
|
12
|
+
* @returns Subscription object with unsubscribe method
|
|
13
|
+
*/
|
|
14
|
+
onImpression(e) {
|
|
15
|
+
return this.subscribers.add(e), {
|
|
16
|
+
unsubscribe: () => {
|
|
17
|
+
this.subscribers.delete(e);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Notifies all subscribers of an impression */
|
|
22
|
+
notifySubscribers(e, i) {
|
|
23
|
+
this.subscribers.forEach((t) => {
|
|
24
|
+
try {
|
|
25
|
+
t(e, i);
|
|
26
|
+
} catch (s) {
|
|
27
|
+
this.debug && console.error("DotCMS Analytics: Error in impression subscriber:", s);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Merges user config with defaults */
|
|
32
|
+
resolveImpressionConfig(e) {
|
|
33
|
+
return typeof e != "object" || e === null ? { ...l } : {
|
|
34
|
+
...l,
|
|
35
|
+
...e
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Initializes tracking: sets up observers, finds contentlets, handles visibility/navigation */
|
|
39
|
+
initialize() {
|
|
40
|
+
if (!(typeof window > "u" || typeof document > "u")) {
|
|
41
|
+
if (d()) {
|
|
42
|
+
this.debug && console.warn("DotCMS Analytics: Impression tracking disabled in editor mode");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.initializeIntersectionObserver(), this.findAndObserveContentletElements(), this.initializeDynamicContentDetector(), this.initializePageVisibilityHandler(), this.initializePageNavigationHandler(), this.debug && console.warn(
|
|
46
|
+
"DotCMS Analytics: Impression tracking initialized with config:",
|
|
47
|
+
this.impressionConfig
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Sets up IntersectionObserver with configured visibility threshold */
|
|
52
|
+
initializeIntersectionObserver() {
|
|
53
|
+
const e = {
|
|
54
|
+
root: null,
|
|
55
|
+
// Use viewport as root
|
|
56
|
+
rootMargin: "0px",
|
|
57
|
+
threshold: this.impressionConfig.visibilityThreshold
|
|
58
|
+
};
|
|
59
|
+
this.observer = new IntersectionObserver((i) => {
|
|
60
|
+
this.processIntersectionChanges(i);
|
|
61
|
+
}, e);
|
|
62
|
+
}
|
|
63
|
+
/** Finds contentlets in DOM, validates them, and starts observing (respects maxNodes limit) */
|
|
64
|
+
findAndObserveContentletElements() {
|
|
65
|
+
if (!this.observer) return;
|
|
66
|
+
const e = document.querySelectorAll(
|
|
67
|
+
`.${a}`
|
|
68
|
+
);
|
|
69
|
+
if (e.length === 0) {
|
|
70
|
+
this.debug && console.warn("DotCMS Analytics: No contentlets found to track");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const i = Math.min(e.length, this.impressionConfig.maxNodes);
|
|
74
|
+
let t = 0;
|
|
75
|
+
for (let s = 0; s < i; s++) {
|
|
76
|
+
const n = e[s], r = c(n);
|
|
77
|
+
if (r) {
|
|
78
|
+
const o = this.shouldSkipElement(n);
|
|
79
|
+
if (o) {
|
|
80
|
+
this.debug && console.warn(
|
|
81
|
+
`DotCMS Analytics: Skipping element ${r} (${o})`
|
|
82
|
+
);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
this.elementImpressionStates.has(r) || (this.observer.observe(n), this.elementImpressionStates.set(r, {
|
|
86
|
+
timer: null,
|
|
87
|
+
visibleSince: null,
|
|
88
|
+
tracked: this.hasBeenTrackedInSession(r),
|
|
89
|
+
element: n
|
|
90
|
+
}), t++, this.debug && (n.dataset.dotAnalyticsObserved = "true", n.style.outline = "2px solid red", n.style.outlineOffset = "-2px", n.style.transition = "outline-color 0.5s ease-in-out"));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
this.debug && (console.warn(`DotCMS Analytics: Observing ${t} contentlets`), e.length > i && console.warn(
|
|
94
|
+
`DotCMS Analytics: ${e.length - i} contentlets not tracked (maxNodes limit: ${this.impressionConfig.maxNodes})`
|
|
95
|
+
));
|
|
96
|
+
}
|
|
97
|
+
/** Watches for new contentlets added to DOM (debounced for performance) */
|
|
98
|
+
initializeDynamicContentDetector() {
|
|
99
|
+
if (typeof window > "u" || typeof document > "u")
|
|
100
|
+
return;
|
|
101
|
+
const e = m(() => {
|
|
102
|
+
this.findAndObserveContentletElements();
|
|
103
|
+
}, u);
|
|
104
|
+
this.mutationObserver = new MutationObserver(() => {
|
|
105
|
+
e();
|
|
106
|
+
}), this.mutationObserver.observe(document.body, {
|
|
107
|
+
childList: !0,
|
|
108
|
+
subtree: !0
|
|
109
|
+
}), this.debug && console.warn(
|
|
110
|
+
"DotCMS Analytics: MutationObserver enabled for dynamic content detection"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
/** Cancels all timers when page is hidden (prevents false impressions) */
|
|
114
|
+
initializePageVisibilityHandler() {
|
|
115
|
+
document.addEventListener("visibilitychange", () => {
|
|
116
|
+
document.visibilityState === "hidden" && (this.elementImpressionStates.forEach((e) => {
|
|
117
|
+
e.timer !== null && (window.clearTimeout(e.timer), e.timer = null, e.visibleSince = null);
|
|
118
|
+
}), this.debug && console.warn("DotCMS Analytics: Page hidden, all impression timers cancelled"));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** Resets tracking on SPA navigation (listens to pushState, replaceState, popstate) */
|
|
122
|
+
initializePageNavigationHandler() {
|
|
123
|
+
this.currentPagePath = window.location.pathname;
|
|
124
|
+
const e = () => {
|
|
125
|
+
const s = window.location.pathname;
|
|
126
|
+
s !== this.currentPagePath && (this.debug && console.warn(
|
|
127
|
+
`DotCMS Analytics: Navigation detected (${this.currentPagePath} → ${s}), resetting impression tracking`
|
|
128
|
+
), this.currentPagePath = s, this.sessionTrackedImpressions.clear(), this.elementImpressionStates.forEach((n) => {
|
|
129
|
+
n.timer !== null && (window.clearTimeout(n.timer), n.timer = null, n.visibleSince = null);
|
|
130
|
+
}), this.elementImpressionStates.clear());
|
|
131
|
+
};
|
|
132
|
+
window.addEventListener("popstate", e);
|
|
133
|
+
const i = history.pushState, t = history.replaceState;
|
|
134
|
+
history.pushState = function(...s) {
|
|
135
|
+
i.apply(this, s), e();
|
|
136
|
+
}, history.replaceState = function(...s) {
|
|
137
|
+
t.apply(this, s), e();
|
|
138
|
+
}, setInterval(e, 1e3);
|
|
139
|
+
}
|
|
140
|
+
/** Handles visibility changes: starts timer on enter, cancels on exit */
|
|
141
|
+
processIntersectionChanges(e) {
|
|
142
|
+
document.visibilityState === "visible" && e.forEach((i) => {
|
|
143
|
+
const t = i.target, s = c(t);
|
|
144
|
+
s && (i.isIntersecting ? this.startImpressionDwellTimer(s, t) : this.cancelImpressionDwellTimer(s));
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** Starts dwell timer; fires impression if element still visible when timer expires */
|
|
148
|
+
startImpressionDwellTimer(e, i) {
|
|
149
|
+
const t = this.elementImpressionStates.get(e);
|
|
150
|
+
t && (t.tracked || this.hasBeenTrackedInSession(e) || t.timer === null && document.visibilityState === "visible" && (t.visibleSince = Date.now(), t.element = i, t.timer = window.setTimeout(() => {
|
|
151
|
+
this.isElementStillVisible(i) ? this.trackAndSendImpression(e, i) : (this.debug && console.warn(
|
|
152
|
+
`DotCMS Analytics: Dwell timer expired for ${e} but element no longer visible, skipping impression`
|
|
153
|
+
), t.timer = null, t.visibleSince = null);
|
|
154
|
+
}, this.impressionConfig.dwellMs), this.debug && console.warn(
|
|
155
|
+
`DotCMS Analytics: Started dwell timer for ${e} (${this.impressionConfig.dwellMs}ms)`
|
|
156
|
+
)));
|
|
157
|
+
}
|
|
158
|
+
/** Cancels active dwell timer (element left viewport before dwell time) */
|
|
159
|
+
cancelImpressionDwellTimer(e) {
|
|
160
|
+
const i = this.elementImpressionStates.get(e);
|
|
161
|
+
!i || i.timer === null || (window.clearTimeout(i.timer), i.timer = null, i.visibleSince = null, this.debug && console.warn(`DotCMS Analytics: Cancelled dwell timer for ${e}`));
|
|
162
|
+
}
|
|
163
|
+
/** Fires impression event with content & position data (page data added by enricher plugin) */
|
|
164
|
+
trackAndSendImpression(e, i) {
|
|
165
|
+
const t = this.elementImpressionStates.get(e);
|
|
166
|
+
if (!t) return;
|
|
167
|
+
const s = t.visibleSince ? Date.now() - t.visibleSince : 0, n = p(i), r = b(i), o = {
|
|
168
|
+
content: {
|
|
169
|
+
identifier: n.identifier,
|
|
170
|
+
inode: n.inode,
|
|
171
|
+
title: n.title,
|
|
172
|
+
content_type: n.contentType
|
|
173
|
+
},
|
|
174
|
+
position: {
|
|
175
|
+
viewport_offset_pct: r.offsetPercentage,
|
|
176
|
+
dom_index: Array.from(
|
|
177
|
+
document.querySelectorAll(`.${a}`)
|
|
178
|
+
).indexOf(i)
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
this.notifySubscribers(h, o), this.markImpressionAsTracked(e), t.timer = null, t.visibleSince = null, t.tracked = !0, this.observer && this.observer.unobserve(i), this.debug && (i.style.outline = "2px solid green", i.style.outlineOffset = "-2px", console.warn(
|
|
182
|
+
`DotCMS Analytics: Fired impression for ${e} (dwell: ${s}ms) - element unobserved`,
|
|
183
|
+
n
|
|
184
|
+
));
|
|
185
|
+
}
|
|
186
|
+
/** Returns skip reason if element is hidden/too small, null if trackable */
|
|
187
|
+
shouldSkipElement(e) {
|
|
188
|
+
const i = e.getBoundingClientRect(), t = window.getComputedStyle(e);
|
|
189
|
+
if (i.height === 0 || i.width === 0)
|
|
190
|
+
return `zero dimensions: ${i.width}x${i.height}`;
|
|
191
|
+
const s = 10;
|
|
192
|
+
return i.height < s || i.width < s ? `too small: ${i.width}x${i.height} (minimum: ${s}px)` : t.visibility === "hidden" ? "visibility: hidden" : parseFloat(t.opacity) === 0 ? "opacity: 0" : t.display === "none" ? "display: none" : null;
|
|
193
|
+
}
|
|
194
|
+
/** Post-dwell check: verifies element still meets visibility threshold */
|
|
195
|
+
isElementStillVisible(e) {
|
|
196
|
+
return document.visibilityState !== "visible" ? !1 : f(
|
|
197
|
+
e,
|
|
198
|
+
this.impressionConfig.visibilityThreshold
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
/** Checks if impression already fired in current page session */
|
|
202
|
+
hasBeenTrackedInSession(e) {
|
|
203
|
+
return this.sessionTrackedImpressions.has(e);
|
|
204
|
+
}
|
|
205
|
+
/** Marks impression as tracked (prevents duplicates in same page session) */
|
|
206
|
+
markImpressionAsTracked(e) {
|
|
207
|
+
this.sessionTrackedImpressions.add(e);
|
|
208
|
+
}
|
|
209
|
+
/** Cleanup: disconnects observers, clears timers and state */
|
|
210
|
+
cleanup() {
|
|
211
|
+
this.observer && (this.observer.disconnect(), this.observer = null), this.mutationObserver && (this.mutationObserver.disconnect(), this.mutationObserver = null), this.elementImpressionStates.forEach((e) => {
|
|
212
|
+
e.timer !== null && window.clearTimeout(e.timer);
|
|
213
|
+
}), this.elementImpressionStates.clear(), this.subscribers.clear(), this.debug && console.warn("DotCMS Analytics: Impression tracking cleaned up");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export {
|
|
217
|
+
I as DotCMSImpressionTracker
|
|
218
|
+
};
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { PageData } from 'analytics';
|
|
2
|
+
import { DotCMSPredefinedEventType } from './constants';
|
|
2
3
|
import { AnalyticsBasePayloadWithContext, DotCMSAnalyticsConfig, DotCMSAnalyticsEventContext, DotCMSBrowserData, DotCMSEventDeviceData, DotCMSEventUtmData, EnrichedAnalyticsPayload } from './models';
|
|
3
4
|
export { cleanupActivityTracking, getLastActivity, getSessionInfo, initializeActivityTracking, isUserInactive, updateSessionActivity } from './dot-content-analytics.activity-tracker';
|
|
5
|
+
/**
|
|
6
|
+
* Type guard to check if an event is a predefined event type.
|
|
7
|
+
* Enables TypeScript type narrowing for better type safety.
|
|
8
|
+
*
|
|
9
|
+
* @param event - Event name to check
|
|
10
|
+
* @returns True if event is a predefined type, false for custom events
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* if (isPredefinedEventType(eventName)) {
|
|
15
|
+
* // TypeScript knows eventName is DotCMSPredefinedEventType here
|
|
16
|
+
* console.log('Predefined event:', eventName);
|
|
17
|
+
* } else {
|
|
18
|
+
* // TypeScript knows eventName is string (custom) here
|
|
19
|
+
* console.log('Custom event:', eventName);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function isPredefinedEventType(event: string): event is DotCMSPredefinedEventType;
|
|
4
24
|
/**
|
|
5
25
|
* Validates required configuration fields for Analytics initialization.
|
|
6
26
|
*
|
|
@@ -89,7 +89,7 @@ export interface DotCMSEventUtmData {
|
|
|
89
89
|
id?: string;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
|
-
* Page data structure for DotCMS analytics.
|
|
92
|
+
* Page data structure for DotCMS analytics (used in pageview events).
|
|
93
93
|
* Contains comprehensive information about the current page and its context
|
|
94
94
|
* within the DotCMS environment.
|
|
95
95
|
*/
|
|
@@ -101,3 +101,41 @@ export type DotCMSEventPageData = Pick<DotCMSBrowserData, 'url' | 'doc_path' | '
|
|
|
101
101
|
/** Persona identifier */
|
|
102
102
|
persona?: string;
|
|
103
103
|
};
|
|
104
|
+
/**
|
|
105
|
+
* Minimal page data for content impression events.
|
|
106
|
+
* Contains only essential page information (title and url) to keep payload lightweight.
|
|
107
|
+
*/
|
|
108
|
+
export type DotCMSContentImpressionPageData = Pick<DotCMSEventPageData, 'title' | 'url'>;
|
|
109
|
+
/**
|
|
110
|
+
* Data structure for content impression events.
|
|
111
|
+
* Tracks when a contentlet becomes visible in the viewport.
|
|
112
|
+
*/
|
|
113
|
+
export interface DotCMSImpressionEventData {
|
|
114
|
+
/** Contentlet identification data extracted from data-dot-analytics-* attributes */
|
|
115
|
+
contentlet: {
|
|
116
|
+
/** Unique identifier of the contentlet */
|
|
117
|
+
identifier: string;
|
|
118
|
+
/** Inode of the contentlet */
|
|
119
|
+
inode: string;
|
|
120
|
+
/** Content type name */
|
|
121
|
+
contentType: string;
|
|
122
|
+
/** Title of the contentlet */
|
|
123
|
+
title: string;
|
|
124
|
+
/** Base type of the contentlet (e.g., CONTENT, WIDGET) */
|
|
125
|
+
baseType: string;
|
|
126
|
+
};
|
|
127
|
+
/** Viewport position and visibility metrics */
|
|
128
|
+
viewport: {
|
|
129
|
+
/** Percentage offset from top of viewport (0-100) */
|
|
130
|
+
offsetPercentage: number;
|
|
131
|
+
/** Percentage of element visible in viewport (0-1) */
|
|
132
|
+
visibilityRatio: number;
|
|
133
|
+
};
|
|
134
|
+
/** Timing information about the impression */
|
|
135
|
+
timing: {
|
|
136
|
+
/** Time in milliseconds the element was continuously visible */
|
|
137
|
+
dwellTime: number;
|
|
138
|
+
/** ISO 8601 timestamp when the impression was fired */
|
|
139
|
+
timestamp: string;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DotCMSEventPageData, DotCMSEventUtmData } from './data.model';
|
|
1
|
+
import { DotCMSContentImpressionPageData, DotCMSEventPageData, DotCMSEventUtmData } from './data.model';
|
|
2
2
|
import { DotCMSCustomEventType, DotCMSEventType, DotCMSPredefinedEventType } from '../constants/dot-content-analytics.constants';
|
|
3
3
|
/**
|
|
4
4
|
* JSON value type for analytics custom data.
|
|
@@ -50,15 +50,53 @@ export type DotCMSCustomEventData = {
|
|
|
50
50
|
/** Custom data associated with the event (any valid JSON) */
|
|
51
51
|
custom: JsonObject;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Partial content impression data sent by producer plugins.
|
|
55
|
+
* Contains only impression-specific data (content and position).
|
|
56
|
+
* The enricher plugin will add page data automatically.
|
|
57
|
+
*/
|
|
58
|
+
export type DotCMSContentImpressionPayload = {
|
|
59
|
+
/** Content information */
|
|
60
|
+
content: {
|
|
61
|
+
/** Content identifier */
|
|
62
|
+
identifier: string;
|
|
63
|
+
/** Content inode */
|
|
64
|
+
inode: string;
|
|
65
|
+
/** Content title */
|
|
66
|
+
title: string;
|
|
67
|
+
/** Content type name */
|
|
68
|
+
content_type: string;
|
|
69
|
+
};
|
|
70
|
+
/** Position information in the viewport and DOM */
|
|
71
|
+
position: {
|
|
72
|
+
/** Viewport offset percentage from top */
|
|
73
|
+
viewport_offset_pct: number;
|
|
74
|
+
/** DOM index position */
|
|
75
|
+
dom_index: number;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Complete data structure for content impression events after enrichment.
|
|
80
|
+
* Includes minimal page data (title and url) added by the enricher plugin.
|
|
81
|
+
*/
|
|
82
|
+
export type DotCMSContentImpressionEventData = DotCMSContentImpressionPayload & {
|
|
83
|
+
/** Minimal page data where the impression occurred (added by enricher) */
|
|
84
|
+
page: DotCMSContentImpressionPageData;
|
|
85
|
+
};
|
|
53
86
|
/**
|
|
54
87
|
* Pageview event structure.
|
|
55
88
|
*/
|
|
56
89
|
export type DotCMSPageViewEvent = DotCMSEventBase<typeof DotCMSPredefinedEventType.PAGEVIEW, DotCMSPageViewEventData>;
|
|
90
|
+
/**
|
|
91
|
+
* Content impression event structure.
|
|
92
|
+
*/
|
|
93
|
+
export type DotCMSContentImpressionEvent = DotCMSEventBase<typeof DotCMSPredefinedEventType.CONTENT_IMPRESSION, DotCMSContentImpressionEventData>;
|
|
57
94
|
/**
|
|
58
95
|
* Custom event structure.
|
|
59
96
|
*/
|
|
60
97
|
export type DotCMSCustomEvent = DotCMSEventBase<DotCMSCustomEventType, DotCMSCustomEventData>;
|
|
61
98
|
/**
|
|
62
99
|
* Union type for all possible analytics events.
|
|
100
|
+
* Used primarily for type documentation and validation.
|
|
63
101
|
*/
|
|
64
|
-
export type DotCMSEvent = DotCMSPageViewEvent | DotCMSCustomEvent;
|
|
102
|
+
export type DotCMSEvent = DotCMSPageViewEvent | DotCMSContentImpressionEvent | DotCMSCustomEvent;
|