@dotcms/analytics 1.2.0 → 1.2.1-next.2

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.
Files changed (52) hide show
  1. package/README.md +314 -9
  2. package/lib/core/dot-analytics.content.js +84 -0
  3. package/lib/core/plugin/click/dot-analytics.click-tracker.d.ts +108 -0
  4. package/lib/core/plugin/click/dot-analytics.click-tracker.js +144 -0
  5. package/lib/core/plugin/click/dot-analytics.click.plugin.d.ts +36 -0
  6. package/lib/core/plugin/click/dot-analytics.click.plugin.js +27 -0
  7. package/lib/core/plugin/click/dot-analytics.click.utils.d.ts +12 -0
  8. package/lib/core/plugin/click/dot-analytics.click.utils.js +55 -0
  9. package/lib/core/plugin/enricher/dot-analytics.enricher.plugin.d.ts +14 -10
  10. package/lib/core/plugin/enricher/dot-analytics.enricher.plugin.js +26 -38
  11. package/lib/core/{shared/dot-content-analytics.activity-tracker.d.ts → plugin/identity/dot-analytics.identity.activity-tracker.d.ts} +2 -17
  12. package/lib/core/{shared/dot-content-analytics.activity-tracker.js → plugin/identity/dot-analytics.identity.activity-tracker.js} +17 -38
  13. package/lib/core/plugin/identity/dot-analytics.identity.plugin.d.ts +2 -20
  14. package/lib/core/plugin/identity/dot-analytics.identity.plugin.js +7 -7
  15. package/lib/core/plugin/identity/dot-analytics.identity.utils.d.ts +0 -16
  16. package/lib/core/plugin/impression/dot-analytics.impression-tracker.d.ts +62 -0
  17. package/lib/core/plugin/impression/dot-analytics.impression-tracker.js +202 -0
  18. package/lib/core/plugin/impression/dot-analytics.impression.plugin.d.ts +40 -0
  19. package/lib/core/plugin/impression/dot-analytics.impression.plugin.js +27 -0
  20. package/lib/core/plugin/impression/dot-analytics.impression.utils.d.ts +26 -0
  21. package/lib/core/plugin/impression/dot-analytics.impression.utils.js +27 -0
  22. package/lib/core/plugin/impression/index.d.ts +2 -0
  23. package/lib/core/plugin/main/dot-analytics.plugin.d.ts +46 -0
  24. package/lib/core/plugin/main/dot-analytics.plugin.js +129 -0
  25. package/lib/core/shared/constants/{dot-content-analytics.constants.d.ts → dot-analytics.constants.d.ts} +62 -0
  26. package/lib/core/shared/constants/dot-analytics.constants.js +53 -0
  27. package/lib/core/shared/constants/index.d.ts +1 -1
  28. package/lib/core/shared/dot-analytics.logger.d.ts +85 -0
  29. package/lib/core/shared/dot-analytics.logger.js +90 -0
  30. package/lib/core/shared/http/dot-analytics.http.d.ts +9 -0
  31. package/lib/core/shared/http/dot-analytics.http.js +34 -0
  32. package/lib/core/shared/models/data.model.d.ts +39 -1
  33. package/lib/core/shared/models/event.model.d.ts +108 -3
  34. package/lib/core/shared/models/library.model.d.ts +89 -28
  35. package/lib/core/shared/models/request.model.d.ts +17 -9
  36. package/lib/core/shared/queue/dot-analytics.queue.utils.js +47 -40
  37. package/lib/core/shared/{dot-content-analytics.utils.d.ts → utils/dot-analytics.utils.d.ts} +91 -3
  38. package/lib/core/shared/utils/dot-analytics.utils.js +200 -0
  39. package/lib/react/hook/useContentAnalytics.js +17 -11
  40. package/lib/react/hook/useRouterTracker.js +12 -12
  41. package/lib/react/internal/utils.js +1 -1
  42. package/package.json +7 -6
  43. package/uve/src/internal/events.js +30 -31
  44. package/uve/src/lib/dom/dom.utils.js +46 -52
  45. package/lib/core/dot-content-analytics.js +0 -46
  46. package/lib/core/plugin/dot-analytics.plugin.d.ts +0 -33
  47. package/lib/core/plugin/dot-analytics.plugin.js +0 -42
  48. package/lib/core/shared/constants/dot-content-analytics.constants.js +0 -34
  49. package/lib/core/shared/dot-content-analytics.http.d.ts +0 -17
  50. package/lib/core/shared/dot-content-analytics.http.js +0 -41
  51. package/lib/core/shared/dot-content-analytics.utils.js +0 -147
  52. /package/lib/core/{dot-content-analytics.d.ts → dot-analytics.content.d.ts} +0 -0
@@ -0,0 +1,62 @@
1
+ import { DotCMSAnalyticsConfig, DotCMSContentImpressionPayload } from '../../shared/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 logger;
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,202 @@
1
+ import { getUVEState as m } from "../../../../uve/src/lib/core/core.utils.js";
2
+ import "../../../../uve/src/internal/constants.js";
3
+ import { getViewportMetrics as d, isElementMeetingVisibilityThreshold as u } from "./dot-analytics.impression.utils.js";
4
+ import { DEFAULT_IMPRESSION_CONFIG as l, IMPRESSION_EVENT_TYPE as g } from "../../shared/constants/dot-analytics.constants.js";
5
+ import { createPluginLogger as p, isBrowser as a, INITIAL_SCAN_DELAY_MS as f, findContentlets as b, extractContentletIdentifier as c, createContentletObserver as v, extractContentletData as I } from "../../shared/utils/dot-analytics.utils.js";
6
+ class E {
7
+ constructor(e) {
8
+ this.observer = null, this.mutationObserver = null, this.elementImpressionStates = /* @__PURE__ */ new Map(), this.sessionTrackedImpressions = /* @__PURE__ */ new Set(), this.currentPagePath = "", this.subscribers = /* @__PURE__ */ new Set(), this.logger = p("Impression", e), this.impressionConfig = this.resolveImpressionConfig(e.impressions);
9
+ }
10
+ /**
11
+ * Subscribe to impression events
12
+ * @param callback - Function called when impression is detected
13
+ * @returns Subscription object with unsubscribe method
14
+ */
15
+ onImpression(e) {
16
+ return this.subscribers.add(e), {
17
+ unsubscribe: () => {
18
+ this.subscribers.delete(e);
19
+ }
20
+ };
21
+ }
22
+ /** Notifies all subscribers of an impression */
23
+ notifySubscribers(e, i) {
24
+ this.subscribers.forEach((t) => {
25
+ try {
26
+ t(e, i);
27
+ } catch (s) {
28
+ this.logger.error("Error in impression subscriber:", s);
29
+ }
30
+ });
31
+ }
32
+ /** Merges user config with defaults */
33
+ resolveImpressionConfig(e) {
34
+ return typeof e != "object" || e === null ? { ...l } : {
35
+ ...l,
36
+ ...e
37
+ };
38
+ }
39
+ /** Initializes tracking: sets up observers, finds contentlets, handles visibility/navigation */
40
+ initialize() {
41
+ if (a()) {
42
+ if (m()) {
43
+ this.logger.warn("Impression tracking disabled in editor mode");
44
+ return;
45
+ }
46
+ this.initializeIntersectionObserver(), typeof window < "u" && setTimeout(() => {
47
+ this.logger.debug("Running initial scan after timeout..."), this.findAndObserveContentletElements();
48
+ }, f), this.initializeDynamicContentDetector(), this.initializePageVisibilityHandler(), this.initializePageNavigationHandler(), this.logger.info("Impression tracking initialized with config:", this.impressionConfig);
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 = b();
67
+ if (e.length === 0) {
68
+ this.logger.warn("No contentlets found to track");
69
+ return;
70
+ }
71
+ const i = Math.min(e.length, this.impressionConfig.maxNodes);
72
+ let t = 0;
73
+ for (let s = 0; s < i; s++) {
74
+ const n = e[s], r = c(n);
75
+ if (r) {
76
+ const o = this.shouldSkipElement(n);
77
+ if (o) {
78
+ this.logger.debug(`Skipping element ${r} (${o})`);
79
+ continue;
80
+ }
81
+ this.elementImpressionStates.has(r) || (n.dataset.dotAnalyticsDomIndex || (n.dataset.dotAnalyticsDomIndex = String(s)), this.observer.observe(n), this.elementImpressionStates.set(r, {
82
+ timer: null,
83
+ visibleSince: null,
84
+ tracked: this.hasBeenTrackedInSession(r),
85
+ element: n
86
+ }), t++);
87
+ }
88
+ }
89
+ this.logger.info(`Observing ${t} contentlets`), e.length > i && this.logger.warn(
90
+ `${e.length - i} contentlets not tracked (maxNodes limit: ${this.impressionConfig.maxNodes})`
91
+ );
92
+ }
93
+ /** Watches for new contentlets added to DOM (debounced for performance) */
94
+ initializeDynamicContentDetector() {
95
+ a() && (this.mutationObserver = v(() => {
96
+ this.findAndObserveContentletElements();
97
+ }), this.logger.info("MutationObserver enabled for dynamic content detection"));
98
+ }
99
+ /** Cancels all timers when page is hidden (prevents false impressions) */
100
+ initializePageVisibilityHandler() {
101
+ document.addEventListener("visibilitychange", () => {
102
+ document.visibilityState === "hidden" && (this.elementImpressionStates.forEach((e) => {
103
+ e.timer !== null && (window.clearTimeout(e.timer), e.timer = null, e.visibleSince = null);
104
+ }), this.logger.warn("Page hidden, all impression timers cancelled"));
105
+ });
106
+ }
107
+ /** Resets tracking on SPA navigation (listens to pushState, replaceState, popstate) */
108
+ initializePageNavigationHandler() {
109
+ this.currentPagePath = window.location.pathname;
110
+ const e = () => {
111
+ const s = window.location.pathname;
112
+ s !== this.currentPagePath && (this.logger.warn(
113
+ `Navigation detected (${this.currentPagePath} → ${s}), resetting impression tracking`
114
+ ), this.currentPagePath = s, this.sessionTrackedImpressions.clear(), this.elementImpressionStates.forEach((n) => {
115
+ n.timer !== null && (window.clearTimeout(n.timer), n.timer = null, n.visibleSince = null);
116
+ }), this.elementImpressionStates.clear());
117
+ };
118
+ window.addEventListener("popstate", e);
119
+ const i = history.pushState, t = history.replaceState;
120
+ history.pushState = function(...s) {
121
+ i.apply(this, s), e();
122
+ }, history.replaceState = function(...s) {
123
+ t.apply(this, s), e();
124
+ };
125
+ }
126
+ /** Handles visibility changes: starts timer on enter, cancels on exit */
127
+ processIntersectionChanges(e) {
128
+ document.visibilityState === "visible" && e.forEach((i) => {
129
+ const t = i.target, s = c(t);
130
+ s && (i.isIntersecting ? this.startImpressionDwellTimer(s, t) : this.cancelImpressionDwellTimer(s));
131
+ });
132
+ }
133
+ /** Starts dwell timer; fires impression if element still visible when timer expires */
134
+ startImpressionDwellTimer(e, i) {
135
+ const t = this.elementImpressionStates.get(e);
136
+ t && (t.tracked || this.hasBeenTrackedInSession(e) || t.timer === null && document.visibilityState === "visible" && (t.visibleSince = Date.now(), t.element = i, t.timer = window.setTimeout(() => {
137
+ this.isElementStillVisible(i) ? this.trackAndSendImpression(e, i) : (this.logger.warn(
138
+ `Dwell timer expired for ${e} but element no longer visible, skipping impression`
139
+ ), t.timer = null, t.visibleSince = null);
140
+ }, this.impressionConfig.dwellMs), this.logger.debug(
141
+ `Started dwell timer for ${e} (${this.impressionConfig.dwellMs}ms)`
142
+ )));
143
+ }
144
+ /** Cancels active dwell timer (element left viewport before dwell time) */
145
+ cancelImpressionDwellTimer(e) {
146
+ const i = this.elementImpressionStates.get(e);
147
+ !i || i.timer === null || (window.clearTimeout(i.timer), i.timer = null, i.visibleSince = null, this.logger.debug(`Cancelled dwell timer for ${e}`));
148
+ }
149
+ /** Fires impression event with content & position data (page data added by enricher plugin) */
150
+ trackAndSendImpression(e, i) {
151
+ const t = this.elementImpressionStates.get(e);
152
+ if (!t) return;
153
+ const s = t.visibleSince ? Date.now() - t.visibleSince : 0, n = I(i), r = d(i), o = parseInt(i.dataset.dotAnalyticsDomIndex || "-1", 10), h = {
154
+ content: {
155
+ identifier: n.identifier,
156
+ inode: n.inode,
157
+ title: n.title,
158
+ content_type: n.contentType
159
+ },
160
+ position: {
161
+ viewport_offset_pct: r.offsetPercentage,
162
+ dom_index: o
163
+ }
164
+ };
165
+ this.notifySubscribers(g, h), this.markImpressionAsTracked(e), t.timer = null, t.visibleSince = null, t.tracked = !0, this.observer && this.observer.unobserve(i), this.logger.info(
166
+ `Fired impression for ${e} (dwell: ${s}ms) - element unobserved`,
167
+ n
168
+ );
169
+ }
170
+ /** Returns skip reason if element is hidden/too small, null if trackable */
171
+ shouldSkipElement(e) {
172
+ const i = e.getBoundingClientRect(), t = window.getComputedStyle(e);
173
+ if (i.height === 0 || i.width === 0)
174
+ return `zero dimensions: ${i.width}x${i.height}`;
175
+ const s = 10;
176
+ 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;
177
+ }
178
+ /** Post-dwell check: verifies element still meets visibility threshold */
179
+ isElementStillVisible(e) {
180
+ return document.visibilityState !== "visible" ? !1 : u(
181
+ e,
182
+ this.impressionConfig.visibilityThreshold
183
+ );
184
+ }
185
+ /** Checks if impression already fired in current page session */
186
+ hasBeenTrackedInSession(e) {
187
+ return this.sessionTrackedImpressions.has(e);
188
+ }
189
+ /** Marks impression as tracked (prevents duplicates in same page session) */
190
+ markImpressionAsTracked(e) {
191
+ this.sessionTrackedImpressions.add(e);
192
+ }
193
+ /** Cleanup: disconnects observers, clears timers and state */
194
+ cleanup() {
195
+ this.observer && (this.observer.disconnect(), this.observer = null), this.mutationObserver && (this.mutationObserver.disconnect(), this.mutationObserver = null), this.elementImpressionStates.forEach((e) => {
196
+ e.timer !== null && window.clearTimeout(e.timer);
197
+ }), this.elementImpressionStates.clear(), this.subscribers.clear(), this.logger.info("Impression tracking cleaned up");
198
+ }
199
+ }
200
+ export {
201
+ E as DotCMSImpressionTracker
202
+ };
@@ -0,0 +1,40 @@
1
+ import { AnalyticsInstance } from 'analytics';
2
+ import { DotCMSAnalyticsConfig } from '../../shared/models';
3
+ /**
4
+ * Impression Plugin for DotAnalytics
5
+ * Handles automatic tracking of content visibility and impressions.
6
+ *
7
+ * This plugin initializes the impression tracker which:
8
+ * - Uses IntersectionObserver to detect when contentlets are visible
9
+ * - Tracks dwell time (how long elements are visible)
10
+ * - Fires 'content-impression' events via instance.track()
11
+ * - Deduplicates impressions per session
12
+ *
13
+ * Plugin execution in Analytics.js pipeline:
14
+ * 1. Identity Plugin - Injects context
15
+ * 2. Enricher Plugin - Enriches event data
16
+ * 3. Main Plugin - Sends to queue/server
17
+ * 4. Impression Plugin - Runs independently, fires events via instance.track()
18
+ *
19
+ * Note: This plugin is only registered if config.impressions is enabled.
20
+ * See getEnhancedTrackingPlugins() for conditional loading logic.
21
+ *
22
+ * @param {DotCMSAnalyticsConfig} config - Configuration with impressions settings
23
+ * @returns {Object} Plugin object with lifecycle methods
24
+ */
25
+ export declare const dotAnalyticsImpressionPlugin: (config: DotCMSAnalyticsConfig) => {
26
+ name: string;
27
+ /**
28
+ * Initialize impression tracking
29
+ * Called when Analytics.js initializes the plugin with instance context
30
+ * @param instance - Analytics.js instance with track method
31
+ */
32
+ initialize: ({ instance }: {
33
+ instance: AnalyticsInstance;
34
+ }) => Promise<void>;
35
+ /**
36
+ * Setup cleanup handlers when plugin is loaded
37
+ * Called after Analytics.js completes plugin loading
38
+ */
39
+ loaded: () => boolean;
40
+ };
@@ -0,0 +1,27 @@
1
+ import { DotCMSImpressionTracker as t } from "./dot-analytics.impression-tracker.js";
2
+ import { createPluginLogger as a, isBrowser as p, setupPluginCleanup as u } from "../../shared/utils/dot-analytics.utils.js";
3
+ const g = (n) => {
4
+ let i = null, e = null;
5
+ const r = a("Impression", n);
6
+ return {
7
+ name: "dot-analytics-impression",
8
+ /**
9
+ * Initialize impression tracking
10
+ * Called when Analytics.js initializes the plugin with instance context
11
+ * @param instance - Analytics.js instance with track method
12
+ */
13
+ initialize: ({ instance: s }) => n.impressions ? (i = new t(n), i.initialize(), e = i.onImpression((o, l) => {
14
+ s.track(o, l);
15
+ }), r.info("Impression tracking plugin initialized"), Promise.resolve()) : (r.info("Impression tracking disabled (config.impressions not set)"), Promise.resolve()),
16
+ /**
17
+ * Setup cleanup handlers when plugin is loaded
18
+ * Called after Analytics.js completes plugin loading
19
+ */
20
+ loaded: () => (p() && i && u(() => {
21
+ e && (e.unsubscribe(), e = null), i && (i.cleanup(), i = null, r.info("Impression tracking cleaned up on page unload"));
22
+ }), !0)
23
+ };
24
+ };
25
+ export {
26
+ g as dotAnalyticsImpressionPlugin
27
+ };
@@ -0,0 +1,26 @@
1
+ import { ViewportMetrics } from '../../shared/models';
2
+ /**
3
+ * Calculates the visibility ratio of an element in the viewport
4
+ * @param element - The HTML element to check
5
+ * @returns A number between 0 and 1 representing the visible percentage
6
+ */
7
+ export declare function calculateElementVisibilityRatio(element: HTMLElement): number;
8
+ /**
9
+ * Calculates the offset percentage of an element from the top of the viewport
10
+ * @param element - The HTML element to check
11
+ * @returns Percentage value (can be negative if above viewport)
12
+ */
13
+ export declare function calculateViewportOffset(element: HTMLElement): number;
14
+ /**
15
+ * Checks if an element meets a specific visibility threshold
16
+ * @param element - The HTML element to check
17
+ * @param threshold - The required visibility ratio (0.0 to 1.0)
18
+ * @returns True if the element meets or exceeds the threshold
19
+ */
20
+ export declare function isElementMeetingVisibilityThreshold(element: HTMLElement, threshold: number): boolean;
21
+ /**
22
+ * Gets comprehensive viewport metrics for an element
23
+ * @param element - The HTML element to analyze
24
+ * @returns Object containing offset percentage and visibility ratio
25
+ */
26
+ export declare function getViewportMetrics(element: HTMLElement): ViewportMetrics;
@@ -0,0 +1,27 @@
1
+ function s(e) {
2
+ const t = e.getBoundingClientRect(), i = window.innerHeight || document.documentElement.clientHeight, n = window.innerWidth || document.documentElement.clientWidth, o = Math.min(t.bottom, i) - Math.max(t.top, 0), c = Math.min(t.right, n) - Math.max(t.left, 0);
3
+ if (o <= 0 || c <= 0)
4
+ return 0;
5
+ const l = o * c, r = t.height * t.width;
6
+ return r > 0 ? l / r : 0;
7
+ }
8
+ function h(e) {
9
+ const t = e.getBoundingClientRect(), i = window.innerHeight || document.documentElement.clientHeight, n = t.top / i * 100;
10
+ return Math.round(n * 100) / 100;
11
+ }
12
+ function a(e, t) {
13
+ return s(e) >= t;
14
+ }
15
+ function u(e) {
16
+ const t = s(e);
17
+ return {
18
+ offsetPercentage: h(e),
19
+ visibilityRatio: t
20
+ };
21
+ }
22
+ export {
23
+ s as calculateElementVisibilityRatio,
24
+ h as calculateViewportOffset,
25
+ u as getViewportMetrics,
26
+ a as isElementMeetingVisibilityThreshold
27
+ };
@@ -0,0 +1,2 @@
1
+ export * from './dot-analytics.impression.plugin';
2
+ export * from './dot-analytics.impression.utils';
@@ -0,0 +1,46 @@
1
+ import { DotCMSAnalyticsConfig, EnrichedAnalyticsPayload, EnrichedTrackPayload } from '../../shared/models';
2
+ /**
3
+ * Analytics plugin for tracking page views and custom events in DotCMS applications.
4
+ * This plugin handles:
5
+ * 1. Event structuring (deciding between predefined and custom events)
6
+ * 2. Building complete request bodies
7
+ * 3. Sending analytics data to the DotCMS server
8
+ * 4. Managing initialization and queue management
9
+ *
10
+ * The enricher plugin runs BEFORE this plugin and adds page/utm/custom data.
11
+ * This plugin receives enriched payloads and structures them into proper events.
12
+ *
13
+ * @param {DotCMSAnalyticsConfig} config - Configuration object containing API key, server URL,
14
+ * debug mode, auto page view settings, and queue config
15
+ * @returns {Object} Plugin object with methods for initialization and event tracking
16
+ */
17
+ export declare const dotAnalytics: (config: DotCMSAnalyticsConfig) => {
18
+ name: string;
19
+ config: DotCMSAnalyticsConfig;
20
+ /**
21
+ * Initialize the plugin with optional queue management
22
+ */
23
+ initialize: () => Promise<void>;
24
+ /**
25
+ * Track a page view event
26
+ * Receives enriched payload from the enricher plugin and structures it into a pageview event
27
+ */
28
+ page: ({ payload }: {
29
+ payload: EnrichedAnalyticsPayload;
30
+ }) => void;
31
+ /**
32
+ * Track a custom or predefined event
33
+ * Receives enriched payload from enricher plugin and structures it into proper event format.
34
+ *
35
+ * - content_impression → extracts from properties, combines with enriched page data
36
+ * - content_click → extracts from properties, combines with enriched page data
37
+ * - custom events → wraps properties in custom object
38
+ */
39
+ track: ({ payload }: {
40
+ payload: EnrichedTrackPayload;
41
+ }) => void;
42
+ /**
43
+ * Check if the plugin is loaded
44
+ */
45
+ loaded: () => boolean;
46
+ };
@@ -0,0 +1,129 @@
1
+ import { DotCMSPredefinedEventType as r } from "../../shared/constants/dot-analytics.constants.js";
2
+ import { sendAnalyticsEvent as N } from "../../shared/http/dot-analytics.http.js";
3
+ import { createAnalyticsQueue as w } from "../../shared/queue/dot-analytics.queue.utils.js";
4
+ const _ = (d) => {
5
+ let u = !1;
6
+ const m = d.queue !== !1;
7
+ let p = null;
8
+ const v = (e) => {
9
+ const c = e.events[0], t = e.context;
10
+ m && p ? p.enqueue(c, t) : N(e, d);
11
+ };
12
+ return {
13
+ name: "dot-analytics",
14
+ config: d,
15
+ /**
16
+ * Initialize the plugin with optional queue management
17
+ */
18
+ initialize: () => (u = !0, m && (p = w(d), p.initialize()), Promise.resolve()),
19
+ /**
20
+ * Track a page view event
21
+ * Receives enriched payload from the enricher plugin and structures it into a pageview event
22
+ */
23
+ page: ({ payload: e }) => {
24
+ if (!u)
25
+ throw new Error("DotCMS Analytics: Plugin not initialized");
26
+ const { context: c, page: t, utm: E, custom: s, local_time: a } = e;
27
+ if (!t)
28
+ throw new Error("DotCMS Analytics: Missing required page data");
29
+ const y = {
30
+ context: c,
31
+ events: [
32
+ {
33
+ event_type: r.PAGEVIEW,
34
+ local_time: a,
35
+ data: {
36
+ page: t,
37
+ ...E && { utm: E },
38
+ ...s && { custom: s }
39
+ }
40
+ }
41
+ ]
42
+ };
43
+ v(y);
44
+ },
45
+ /**
46
+ * Track a custom or predefined event
47
+ * Receives enriched payload from enricher plugin and structures it into proper event format.
48
+ *
49
+ * - content_impression → extracts from properties, combines with enriched page data
50
+ * - content_click → extracts from properties, combines with enriched page data
51
+ * - custom events → wraps properties in custom object
52
+ */
53
+ track: ({ payload: e }) => {
54
+ if (!u)
55
+ throw new Error("DotCMS Analytics: Plugin not initialized");
56
+ const { event: c, properties: t, context: E, local_time: s } = e;
57
+ let a;
58
+ switch (c) {
59
+ case r.CONTENT_IMPRESSION: {
60
+ const l = t, { content: n, position: i } = l, { page: o } = e;
61
+ if (!n || !i || !o)
62
+ throw new Error("DotCMS Analytics: Missing required impression data");
63
+ a = {
64
+ event_type: r.CONTENT_IMPRESSION,
65
+ local_time: s,
66
+ data: {
67
+ content: n,
68
+ position: i,
69
+ page: o
70
+ }
71
+ };
72
+ break;
73
+ }
74
+ case r.CONTENT_CLICK: {
75
+ const l = t, { content: n, position: i, element: o } = l, { page: C } = e;
76
+ if (!n || !i || !o || !C)
77
+ throw new Error("DotCMS Analytics: Missing required click data");
78
+ a = {
79
+ event_type: r.CONTENT_CLICK,
80
+ local_time: s,
81
+ data: {
82
+ content: n,
83
+ position: i,
84
+ element: o,
85
+ page: C
86
+ }
87
+ };
88
+ break;
89
+ }
90
+ case r.CONVERSION: {
91
+ const l = t, { name: n, custom: i } = l, { page: o } = e;
92
+ if (!n || !o)
93
+ throw new Error("DotCMS Analytics: Missing required conversion data");
94
+ a = {
95
+ event_type: r.CONVERSION,
96
+ local_time: s,
97
+ data: {
98
+ conversion: { name: n },
99
+ page: o,
100
+ ...i && { custom: i }
101
+ }
102
+ };
103
+ break;
104
+ }
105
+ default: {
106
+ a = {
107
+ event_type: c,
108
+ local_time: s,
109
+ data: {
110
+ custom: t
111
+ }
112
+ };
113
+ break;
114
+ }
115
+ }
116
+ v({
117
+ context: E,
118
+ events: [a]
119
+ });
120
+ },
121
+ /**
122
+ * Check if the plugin is loaded
123
+ */
124
+ loaded: () => u
125
+ };
126
+ };
127
+ export {
128
+ _ as dotAnalytics
129
+ };
@@ -7,6 +7,9 @@ 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";
11
+ readonly CONTENT_CLICK: "content_click";
12
+ readonly CONVERSION: "conversion";
10
13
  };
11
14
  /**
12
15
  * Type for structured events
@@ -68,3 +71,62 @@ export declare const ANALYTICS_MINIFIED_SCRIPT_NAME = "ca.min.js";
68
71
  * These should be filtered out to only keep user-provided properties
69
72
  */
70
73
  export declare const ANALYTICS_JS_DEFAULT_PROPERTIES: readonly ["title", "url", "path", "hash", "search", "width", "height", "referrer"];
74
+ /**
75
+ * Impression tracking configuration constants
76
+ */
77
+ /**
78
+ * Default minimum percentage of element that must be visible (0.0 to 1.0)
79
+ */
80
+ export declare const DEFAULT_IMPRESSION_VISIBILITY_THRESHOLD = 0.5;
81
+ /**
82
+ * Default minimum time in milliseconds element must be visible
83
+ */
84
+ export declare const DEFAULT_IMPRESSION_DWELL_MS = 750;
85
+ /**
86
+ * Default maximum number of elements to track (performance limit)
87
+ */
88
+ export declare const DEFAULT_IMPRESSION_MAX_NODES = 100;
89
+ /**
90
+ * Default throttle time in milliseconds for intersection callbacks
91
+ */
92
+ export declare const DEFAULT_IMPRESSION_THROTTLE_MS = 100;
93
+ /**
94
+ * Default debounce time in milliseconds for MutationObserver
95
+ */
96
+ export declare const DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS = 250;
97
+ /**
98
+ * Default impression tracking configuration
99
+ */
100
+ export declare const DEFAULT_IMPRESSION_CONFIG: {
101
+ readonly visibilityThreshold: 0.5;
102
+ readonly dwellMs: 750;
103
+ readonly maxNodes: 100;
104
+ readonly throttleMs: 100;
105
+ };
106
+ /**
107
+ * Event type for content impressions
108
+ * Must match DotCMSPredefinedEventType.CONTENT_IMPRESSION
109
+ */
110
+ export declare const IMPRESSION_EVENT_TYPE = "content_impression";
111
+ /**
112
+ * Event type for content clicks
113
+ * Must match DotCMSPredefinedEventType.CONTENT_CLICK
114
+ */
115
+ export declare const CLICK_EVENT_TYPE = "content_click";
116
+ /**
117
+ * Default debounce time in milliseconds for clicks
118
+ */
119
+ export declare const DEFAULT_CLICK_THROTTLE_MS = 300;
120
+ /**
121
+ * CSS selector for clickable elements to track
122
+ * Only clicks on <a> and <button> elements are tracked
123
+ */
124
+ export declare const CLICKABLE_ELEMENTS_SELECTOR = "a, button";
125
+ /**
126
+ * Session storage key for tracked impressions (deduplication)
127
+ */
128
+ export declare const IMPRESSION_SESSION_KEY = "dot_analytics_impressions";
129
+ /**
130
+ * CSS class selector for trackable contentlets
131
+ */
132
+ export declare const ANALYTICS_CONTENTLET_CLASS = "dotcms-analytics-contentlet";