@dotcms/analytics 1.1.1 → 1.2.0-next.10
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 +475 -72
- package/lib/core/{dot-content-analytics.d.ts → dot-analytics.content.d.ts} +8 -1
- package/lib/core/dot-analytics.content.js +63 -0
- package/lib/core/plugin/click/dot-analytics.click-tracker.d.ts +108 -0
- package/lib/core/plugin/click/dot-analytics.click-tracker.js +144 -0
- package/lib/core/plugin/click/dot-analytics.click.plugin.d.ts +36 -0
- package/lib/core/plugin/click/dot-analytics.click.plugin.js +27 -0
- package/lib/core/plugin/click/dot-analytics.click.utils.d.ts +12 -0
- package/lib/core/plugin/click/dot-analytics.click.utils.js +55 -0
- package/lib/core/plugin/enricher/dot-analytics.enricher.plugin.d.ts +18 -30
- package/lib/core/plugin/enricher/dot-analytics.enricher.plugin.js +30 -20
- package/lib/core/plugin/identity/dot-analytics.identity.activity-tracker.d.ts +20 -0
- package/lib/core/{shared/dot-content-analytics.activity-tracker.js → plugin/identity/dot-analytics.identity.activity-tracker.js} +19 -38
- package/lib/core/plugin/identity/dot-analytics.identity.plugin.d.ts +13 -31
- package/lib/core/plugin/identity/dot-analytics.identity.plugin.js +14 -12
- package/lib/core/plugin/identity/dot-analytics.identity.utils.d.ts +7 -22
- package/lib/core/plugin/impression/dot-analytics.impression-tracker.d.ts +62 -0
- package/lib/core/plugin/impression/dot-analytics.impression-tracker.js +200 -0
- package/lib/core/plugin/impression/dot-analytics.impression.plugin.d.ts +40 -0
- package/lib/core/plugin/impression/dot-analytics.impression.plugin.js +27 -0
- package/lib/core/plugin/impression/dot-analytics.impression.utils.d.ts +26 -0
- package/lib/core/plugin/impression/dot-analytics.impression.utils.js +27 -0
- package/lib/core/plugin/impression/index.d.ts +2 -0
- package/lib/core/plugin/main/dot-analytics.plugin.d.ts +46 -0
- package/lib/core/plugin/main/dot-analytics.plugin.js +114 -0
- package/lib/core/shared/constants/dot-analytics.constants.d.ts +131 -0
- package/lib/core/shared/constants/dot-analytics.constants.js +52 -0
- package/lib/core/shared/constants/index.d.ts +4 -0
- package/lib/core/shared/dot-analytics.logger.d.ts +85 -0
- package/lib/core/shared/dot-analytics.logger.js +90 -0
- package/lib/core/shared/http/dot-analytics.http.d.ts +9 -0
- package/lib/core/shared/http/dot-analytics.http.js +34 -0
- package/lib/core/shared/models/data.model.d.ts +141 -0
- package/lib/core/shared/models/event.model.d.ts +135 -0
- package/lib/core/shared/models/index.d.ts +7 -0
- package/lib/core/shared/models/library.model.d.ts +243 -0
- package/lib/core/shared/models/request.model.d.ts +32 -0
- package/lib/core/shared/queue/dot-analytics.queue.utils.d.ts +28 -0
- package/lib/core/shared/queue/dot-analytics.queue.utils.js +80 -0
- package/lib/core/shared/queue/index.d.ts +1 -0
- package/lib/core/shared/utils/dot-analytics.utils.d.ts +260 -0
- package/lib/core/shared/utils/dot-analytics.utils.js +202 -0
- package/lib/react/components/DotContentAnalytics.d.ts +1 -1
- package/lib/react/hook/useContentAnalytics.d.ts +43 -15
- package/lib/react/hook/useContentAnalytics.js +18 -21
- package/lib/react/hook/useRouterTracker.d.ts +1 -1
- package/lib/react/hook/useRouterTracker.js +4 -4
- package/lib/react/internal/utils.d.ts +1 -1
- package/lib/react/internal/utils.js +3 -3
- package/lib/react/public-api.d.ts +1 -1
- package/lib/standalone.d.ts +2 -2
- package/package.json +7 -5
- package/uve/src/internal/constants.js +8 -3
- package/lib/core/dot-content-analytics.js +0 -43
- package/lib/core/plugin/dot-analytics.plugin.d.ts +0 -32
- package/lib/core/plugin/dot-analytics.plugin.js +0 -79
- package/lib/core/shared/dot-content-analytics.activity-tracker.d.ts +0 -28
- package/lib/core/shared/dot-content-analytics.constants.d.ts +0 -37
- package/lib/core/shared/dot-content-analytics.constants.js +0 -14
- package/lib/core/shared/dot-content-analytics.http.d.ts +0 -8
- package/lib/core/shared/dot-content-analytics.http.js +0 -29
- package/lib/core/shared/dot-content-analytics.model.d.ts +0 -351
- package/lib/core/shared/dot-content-analytics.utils.d.ts +0 -111
- package/lib/core/shared/dot-content-analytics.utils.js +0 -123
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { ANALYTICS_WINDOWS_ACTIVE_KEY as a, ANALYTICS_WINDOWS_CLEANUP_KEY as o } from "../../../../uve/src/internal/constants.js";
|
|
2
|
+
import { DEFAULT_SESSION_TIMEOUT_MINUTES as r, ACTIVITY_EVENTS as l } from "../../shared/constants/dot-analytics.constants.js";
|
|
3
|
+
class v {
|
|
3
4
|
constructor() {
|
|
4
5
|
this.activityListeners = [], this.lastActivityTime = Date.now(), this.inactivityTimer = null, this.isThrottled = !1, this.config = null, this.ACTIVITY_THROTTLE_MS = 1e3;
|
|
5
6
|
}
|
|
@@ -11,24 +12,11 @@ class o {
|
|
|
11
12
|
this.lastActivityTime = Date.now(), this.inactivityTimer && clearTimeout(this.inactivityTimer), this.inactivityTimer = setTimeout(
|
|
12
13
|
() => {
|
|
13
14
|
var i;
|
|
14
|
-
(i = this.config) != null && i.debug && console.warn("DotCMS Analytics: User became inactive after timeout"), this.inactivityTimer = null;
|
|
15
|
+
(i = this.config) != null && i.debug && console.warn("DotCMS Analytics [Activity]: User became inactive after timeout"), this.inactivityTimer = null;
|
|
15
16
|
},
|
|
16
|
-
|
|
17
|
+
r * 60 * 1e3
|
|
17
18
|
);
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Checks if user has been inactive
|
|
21
|
-
*/
|
|
22
|
-
isUserInactive() {
|
|
23
|
-
const i = c * 60 * 1e3;
|
|
24
|
-
return Date.now() - this.lastActivityTime > i;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Gets last activity time
|
|
28
|
-
*/
|
|
29
|
-
getLastActivity() {
|
|
30
|
-
return this.lastActivityTime;
|
|
31
|
-
}
|
|
32
20
|
/**
|
|
33
21
|
* Updates session activity with throttling
|
|
34
22
|
*/
|
|
@@ -44,17 +32,19 @@ class o {
|
|
|
44
32
|
if (this.cleanup(), this.config = i, typeof window > "u")
|
|
45
33
|
return;
|
|
46
34
|
const s = () => this.updateSessionActivity();
|
|
47
|
-
|
|
48
|
-
window.addEventListener(
|
|
49
|
-
() => window.removeEventListener(
|
|
35
|
+
l.forEach((c) => {
|
|
36
|
+
window.addEventListener(c, s, { passive: !0 }), this.activityListeners.push(
|
|
37
|
+
() => window.removeEventListener(c, s)
|
|
50
38
|
);
|
|
51
39
|
});
|
|
52
40
|
const n = () => {
|
|
53
|
-
document.visibilityState === "visible" && (this.updateSessionActivity(), i.debug && console.warn(
|
|
41
|
+
document.visibilityState === "visible" && (this.updateSessionActivity(), i.debug && console.warn(
|
|
42
|
+
"DotCMS Analytics [Activity]: User returned to tab, session reactivated"
|
|
43
|
+
));
|
|
54
44
|
};
|
|
55
45
|
document.addEventListener("visibilitychange", n), this.activityListeners.push(
|
|
56
46
|
() => document.removeEventListener("visibilitychange", n)
|
|
57
|
-
), this.updateActivityTime(), i.debug && console.warn("DotCMS Analytics: Activity tracking initialized");
|
|
47
|
+
), this.updateActivityTime(), i.debug && console.warn("DotCMS Analytics [Activity]: Activity tracking initialized");
|
|
58
48
|
}
|
|
59
49
|
/**
|
|
60
50
|
* Cleans up all activity tracking listeners
|
|
@@ -62,25 +52,16 @@ class o {
|
|
|
62
52
|
cleanup() {
|
|
63
53
|
this.activityListeners.forEach((i) => i()), this.activityListeners = [], this.inactivityTimer && (clearTimeout(this.inactivityTimer), this.inactivityTimer = null), this.config = null;
|
|
64
54
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Gets session information for debugging
|
|
67
|
-
*/
|
|
68
|
-
getSessionInfo() {
|
|
69
|
-
return {
|
|
70
|
-
lastActivity: this.getLastActivity(),
|
|
71
|
-
isActive: !this.isUserInactive()
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
55
|
}
|
|
75
|
-
const t = new
|
|
56
|
+
const t = new v(), d = () => {
|
|
76
57
|
t.updateSessionActivity();
|
|
77
|
-
},
|
|
58
|
+
}, u = (e) => {
|
|
78
59
|
t.initialize(e);
|
|
79
|
-
},
|
|
80
|
-
t.cleanup();
|
|
60
|
+
}, y = () => {
|
|
61
|
+
t.cleanup(), typeof window < "u" && (window[a] = !1, window[o] = void 0, window.dispatchEvent(new CustomEvent("dotcms:analytics:cleanup")));
|
|
81
62
|
};
|
|
82
63
|
export {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
64
|
+
y as cleanupActivityTracking,
|
|
65
|
+
u as initializeActivityTracking,
|
|
66
|
+
d as updateSessionActivity
|
|
86
67
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnalyticsBaseParams, DotCMSAnalyticsConfig } from '../../shared/models';
|
|
2
2
|
/**
|
|
3
3
|
* Identity Plugin for DotAnalytics
|
|
4
4
|
* Handles user ID generation, session management, and activity tracking.
|
|
@@ -20,24 +20,15 @@ export declare const dotAnalyticsIdentityPlugin: (config: DotCMSAnalyticsConfig)
|
|
|
20
20
|
*/
|
|
21
21
|
initialize: () => Promise<void>;
|
|
22
22
|
/**
|
|
23
|
-
* Inject identity context into page events
|
|
23
|
+
* Inject identity context into page events and updates session activity for session management
|
|
24
24
|
* This runs BEFORE the enricher plugin
|
|
25
25
|
*/
|
|
26
|
-
pageStart: ({ payload }:
|
|
27
|
-
context: import('../../shared/
|
|
28
|
-
type:
|
|
29
|
-
properties:
|
|
30
|
-
title: string;
|
|
31
|
-
url: string;
|
|
32
|
-
path: string;
|
|
33
|
-
hash: string;
|
|
34
|
-
search: string;
|
|
35
|
-
width: number;
|
|
36
|
-
height: number;
|
|
37
|
-
referrer?: string;
|
|
38
|
-
};
|
|
26
|
+
pageStart: ({ payload }: AnalyticsBaseParams) => {
|
|
27
|
+
context: import('../../shared/models').DotCMSAnalyticsEventContext;
|
|
28
|
+
type: "page" | "track";
|
|
29
|
+
properties: Record<string, unknown>;
|
|
39
30
|
options: Record<string, unknown>;
|
|
40
|
-
userId: string
|
|
31
|
+
userId: string;
|
|
41
32
|
anonymousId: string;
|
|
42
33
|
meta: {
|
|
43
34
|
rid: string;
|
|
@@ -46,24 +37,15 @@ export declare const dotAnalyticsIdentityPlugin: (config: DotCMSAnalyticsConfig)
|
|
|
46
37
|
};
|
|
47
38
|
};
|
|
48
39
|
/**
|
|
49
|
-
* Inject identity context into track events
|
|
40
|
+
* Inject identity context into track events and updates session activity for session management
|
|
50
41
|
* This runs BEFORE the enricher plugin
|
|
51
42
|
*/
|
|
52
|
-
trackStart: ({ payload }:
|
|
53
|
-
context: import('../../shared/
|
|
54
|
-
type:
|
|
55
|
-
properties:
|
|
56
|
-
title: string;
|
|
57
|
-
url: string;
|
|
58
|
-
path: string;
|
|
59
|
-
hash: string;
|
|
60
|
-
search: string;
|
|
61
|
-
width: number;
|
|
62
|
-
height: number;
|
|
63
|
-
referrer?: string;
|
|
64
|
-
};
|
|
43
|
+
trackStart: ({ payload }: AnalyticsBaseParams) => {
|
|
44
|
+
context: import('../../shared/models').DotCMSAnalyticsEventContext;
|
|
45
|
+
type: "page" | "track";
|
|
46
|
+
properties: Record<string, unknown>;
|
|
65
47
|
options: Record<string, unknown>;
|
|
66
|
-
userId: string
|
|
48
|
+
userId: string;
|
|
67
49
|
anonymousId: string;
|
|
68
50
|
meta: {
|
|
69
51
|
rid: string;
|
|
@@ -1,40 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
const
|
|
1
|
+
import { cleanupActivityTracking as n, updateSessionActivity as r, initializeActivityTracking as a } from "./dot-analytics.identity.activity-tracker.js";
|
|
2
|
+
import { getAnalyticsContext as o } from "../../shared/utils/dot-analytics.utils.js";
|
|
3
|
+
const s = (t) => ({
|
|
4
4
|
name: "dot-analytics-identity",
|
|
5
5
|
/**
|
|
6
6
|
* Initialize the identity plugin
|
|
7
7
|
* Sets up activity tracking for session management
|
|
8
8
|
*/
|
|
9
|
-
initialize: () => (
|
|
9
|
+
initialize: () => (a(t), Promise.resolve()),
|
|
10
10
|
/**
|
|
11
|
-
* Inject identity context into page events
|
|
11
|
+
* Inject identity context into page events and updates session activity for session management
|
|
12
12
|
* This runs BEFORE the enricher plugin
|
|
13
13
|
*/
|
|
14
14
|
pageStart: ({ payload: e }) => {
|
|
15
|
-
|
|
15
|
+
r();
|
|
16
|
+
const i = o(t);
|
|
16
17
|
return {
|
|
17
18
|
...e,
|
|
18
|
-
context:
|
|
19
|
+
context: i
|
|
19
20
|
};
|
|
20
21
|
},
|
|
21
22
|
/**
|
|
22
|
-
* Inject identity context into track events
|
|
23
|
+
* Inject identity context into track events and updates session activity for session management
|
|
23
24
|
* This runs BEFORE the enricher plugin
|
|
24
25
|
*/
|
|
25
26
|
trackStart: ({ payload: e }) => {
|
|
26
|
-
|
|
27
|
+
r();
|
|
28
|
+
const i = o(t);
|
|
27
29
|
return {
|
|
28
30
|
...e,
|
|
29
|
-
context:
|
|
31
|
+
context: i
|
|
30
32
|
};
|
|
31
33
|
},
|
|
32
34
|
/**
|
|
33
35
|
* Clean up on plugin unload
|
|
34
36
|
* Sets up cleanup handlers for activity tracking
|
|
35
37
|
*/
|
|
36
|
-
loaded: () => (typeof window < "u" && (window.addEventListener("beforeunload",
|
|
38
|
+
loaded: () => (typeof window < "u" && (window.addEventListener("beforeunload", n), window.addEventListener("pagehide", n)), !0)
|
|
37
39
|
});
|
|
38
40
|
export {
|
|
39
|
-
|
|
41
|
+
s as dotAnalyticsIdentityPlugin
|
|
40
42
|
};
|
|
@@ -1,24 +1,9 @@
|
|
|
1
|
+
import { DotCMSEventUtmData } from '../../shared/models';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* Compares UTM parameters to detect campaign changes.
|
|
4
|
+
* Only checks significant parameters: source, medium, and campaign.
|
|
5
|
+
* @internal This function is for internal use only.
|
|
6
|
+
* @param currentUTM - Current UTM parameters in DotCMS format
|
|
7
|
+
* @returns True if UTM parameters have changed, false otherwise
|
|
3
8
|
*/
|
|
4
|
-
export declare const
|
|
5
|
-
/**
|
|
6
|
-
* Checks if user has been inactive
|
|
7
|
-
*/
|
|
8
|
-
export declare const isUserInactive: () => boolean;
|
|
9
|
-
/**
|
|
10
|
-
* Checks if a new day has started since session creation
|
|
11
|
-
*/
|
|
12
|
-
export declare const hasPassedMidnight: (sessionStartTime: number) => boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Gets the last activity time
|
|
15
|
-
*/
|
|
16
|
-
export declare const getLastActivityTime: () => number;
|
|
17
|
-
/**
|
|
18
|
-
* Extracts UTM parameters from current location
|
|
19
|
-
*/
|
|
20
|
-
export declare const extractUTMParameters: () => Record<string, string>;
|
|
21
|
-
/**
|
|
22
|
-
* Compares UTM parameters to detect campaign changes
|
|
23
|
-
*/
|
|
24
|
-
export declare const hasUTMChanged: (currentUTM: Record<string, string>) => boolean;
|
|
9
|
+
export declare const hasUTMChanged: (currentUTM: DotCMSEventUtmData) => boolean;
|
|
@@ -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,200 @@
|
|
|
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, findContentlets as b, extractContentletIdentifier as c, createContentletObserver as f, extractContentletData as v } from "../../shared/utils/dot-analytics.utils.js";
|
|
6
|
+
class C {
|
|
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(), this.findAndObserveContentletElements(), this.initializeDynamicContentDetector(), this.initializePageVisibilityHandler(), this.initializePageNavigationHandler(), this.logger.info("Impression tracking initialized with config:", this.impressionConfig);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Sets up IntersectionObserver with configured visibility threshold */
|
|
50
|
+
initializeIntersectionObserver() {
|
|
51
|
+
const e = {
|
|
52
|
+
root: null,
|
|
53
|
+
// Use viewport as root
|
|
54
|
+
rootMargin: "0px",
|
|
55
|
+
threshold: this.impressionConfig.visibilityThreshold
|
|
56
|
+
};
|
|
57
|
+
this.observer = new IntersectionObserver((i) => {
|
|
58
|
+
this.processIntersectionChanges(i);
|
|
59
|
+
}, e);
|
|
60
|
+
}
|
|
61
|
+
/** Finds contentlets in DOM, validates them, and starts observing (respects maxNodes limit) */
|
|
62
|
+
findAndObserveContentletElements() {
|
|
63
|
+
if (!this.observer) return;
|
|
64
|
+
const e = b();
|
|
65
|
+
if (e.length === 0) {
|
|
66
|
+
this.logger.warn("No contentlets found to track");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const i = Math.min(e.length, this.impressionConfig.maxNodes);
|
|
70
|
+
let t = 0;
|
|
71
|
+
for (let s = 0; s < i; s++) {
|
|
72
|
+
const n = e[s], r = c(n);
|
|
73
|
+
if (r) {
|
|
74
|
+
const o = this.shouldSkipElement(n);
|
|
75
|
+
if (o) {
|
|
76
|
+
this.logger.debug(`Skipping element ${r} (${o})`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
this.elementImpressionStates.has(r) || (n.dataset.dotAnalyticsDomIndex || (n.dataset.dotAnalyticsDomIndex = String(s)), this.observer.observe(n), this.elementImpressionStates.set(r, {
|
|
80
|
+
timer: null,
|
|
81
|
+
visibleSince: null,
|
|
82
|
+
tracked: this.hasBeenTrackedInSession(r),
|
|
83
|
+
element: n
|
|
84
|
+
}), t++);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
this.logger.info(`Observing ${t} contentlets`), e.length > i && this.logger.warn(
|
|
88
|
+
`${e.length - i} contentlets not tracked (maxNodes limit: ${this.impressionConfig.maxNodes})`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
/** Watches for new contentlets added to DOM (debounced for performance) */
|
|
92
|
+
initializeDynamicContentDetector() {
|
|
93
|
+
a() && (this.mutationObserver = f(() => {
|
|
94
|
+
this.findAndObserveContentletElements();
|
|
95
|
+
}), this.logger.info("MutationObserver enabled for dynamic content detection"));
|
|
96
|
+
}
|
|
97
|
+
/** Cancels all timers when page is hidden (prevents false impressions) */
|
|
98
|
+
initializePageVisibilityHandler() {
|
|
99
|
+
document.addEventListener("visibilitychange", () => {
|
|
100
|
+
document.visibilityState === "hidden" && (this.elementImpressionStates.forEach((e) => {
|
|
101
|
+
e.timer !== null && (window.clearTimeout(e.timer), e.timer = null, e.visibleSince = null);
|
|
102
|
+
}), this.logger.warn("Page hidden, all impression timers cancelled"));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** Resets tracking on SPA navigation (listens to pushState, replaceState, popstate) */
|
|
106
|
+
initializePageNavigationHandler() {
|
|
107
|
+
this.currentPagePath = window.location.pathname;
|
|
108
|
+
const e = () => {
|
|
109
|
+
const s = window.location.pathname;
|
|
110
|
+
s !== this.currentPagePath && (this.logger.warn(
|
|
111
|
+
`Navigation detected (${this.currentPagePath} → ${s}), resetting impression tracking`
|
|
112
|
+
), this.currentPagePath = s, this.sessionTrackedImpressions.clear(), this.elementImpressionStates.forEach((n) => {
|
|
113
|
+
n.timer !== null && (window.clearTimeout(n.timer), n.timer = null, n.visibleSince = null);
|
|
114
|
+
}), this.elementImpressionStates.clear());
|
|
115
|
+
};
|
|
116
|
+
window.addEventListener("popstate", e);
|
|
117
|
+
const i = history.pushState, t = history.replaceState;
|
|
118
|
+
history.pushState = function(...s) {
|
|
119
|
+
i.apply(this, s), e();
|
|
120
|
+
}, history.replaceState = function(...s) {
|
|
121
|
+
t.apply(this, s), e();
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** Handles visibility changes: starts timer on enter, cancels on exit */
|
|
125
|
+
processIntersectionChanges(e) {
|
|
126
|
+
document.visibilityState === "visible" && e.forEach((i) => {
|
|
127
|
+
const t = i.target, s = c(t);
|
|
128
|
+
s && (i.isIntersecting ? this.startImpressionDwellTimer(s, t) : this.cancelImpressionDwellTimer(s));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Starts dwell timer; fires impression if element still visible when timer expires */
|
|
132
|
+
startImpressionDwellTimer(e, i) {
|
|
133
|
+
const t = this.elementImpressionStates.get(e);
|
|
134
|
+
t && (t.tracked || this.hasBeenTrackedInSession(e) || t.timer === null && document.visibilityState === "visible" && (t.visibleSince = Date.now(), t.element = i, t.timer = window.setTimeout(() => {
|
|
135
|
+
this.isElementStillVisible(i) ? this.trackAndSendImpression(e, i) : (this.logger.warn(
|
|
136
|
+
`Dwell timer expired for ${e} but element no longer visible, skipping impression`
|
|
137
|
+
), t.timer = null, t.visibleSince = null);
|
|
138
|
+
}, this.impressionConfig.dwellMs), this.logger.debug(
|
|
139
|
+
`Started dwell timer for ${e} (${this.impressionConfig.dwellMs}ms)`
|
|
140
|
+
)));
|
|
141
|
+
}
|
|
142
|
+
/** Cancels active dwell timer (element left viewport before dwell time) */
|
|
143
|
+
cancelImpressionDwellTimer(e) {
|
|
144
|
+
const i = this.elementImpressionStates.get(e);
|
|
145
|
+
!i || i.timer === null || (window.clearTimeout(i.timer), i.timer = null, i.visibleSince = null, this.logger.debug(`Cancelled dwell timer for ${e}`));
|
|
146
|
+
}
|
|
147
|
+
/** Fires impression event with content & position data (page data added by enricher plugin) */
|
|
148
|
+
trackAndSendImpression(e, i) {
|
|
149
|
+
const t = this.elementImpressionStates.get(e);
|
|
150
|
+
if (!t) return;
|
|
151
|
+
const s = t.visibleSince ? Date.now() - t.visibleSince : 0, n = v(i), r = d(i), o = parseInt(i.dataset.dotAnalyticsDomIndex || "-1", 10), h = {
|
|
152
|
+
content: {
|
|
153
|
+
identifier: n.identifier,
|
|
154
|
+
inode: n.inode,
|
|
155
|
+
title: n.title,
|
|
156
|
+
content_type: n.contentType
|
|
157
|
+
},
|
|
158
|
+
position: {
|
|
159
|
+
viewport_offset_pct: r.offsetPercentage,
|
|
160
|
+
dom_index: o
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
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(
|
|
164
|
+
`Fired impression for ${e} (dwell: ${s}ms) - element unobserved`,
|
|
165
|
+
n
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
/** Returns skip reason if element is hidden/too small, null if trackable */
|
|
169
|
+
shouldSkipElement(e) {
|
|
170
|
+
const i = e.getBoundingClientRect(), t = window.getComputedStyle(e);
|
|
171
|
+
if (i.height === 0 || i.width === 0)
|
|
172
|
+
return `zero dimensions: ${i.width}x${i.height}`;
|
|
173
|
+
const s = 10;
|
|
174
|
+
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;
|
|
175
|
+
}
|
|
176
|
+
/** Post-dwell check: verifies element still meets visibility threshold */
|
|
177
|
+
isElementStillVisible(e) {
|
|
178
|
+
return document.visibilityState !== "visible" ? !1 : u(
|
|
179
|
+
e,
|
|
180
|
+
this.impressionConfig.visibilityThreshold
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
/** Checks if impression already fired in current page session */
|
|
184
|
+
hasBeenTrackedInSession(e) {
|
|
185
|
+
return this.sessionTrackedImpressions.has(e);
|
|
186
|
+
}
|
|
187
|
+
/** Marks impression as tracked (prevents duplicates in same page session) */
|
|
188
|
+
markImpressionAsTracked(e) {
|
|
189
|
+
this.sessionTrackedImpressions.add(e);
|
|
190
|
+
}
|
|
191
|
+
/** Cleanup: disconnects observers, clears timers and state */
|
|
192
|
+
cleanup() {
|
|
193
|
+
this.observer && (this.observer.disconnect(), this.observer = null), this.mutationObserver && (this.mutationObserver.disconnect(), this.mutationObserver = null), this.elementImpressionStates.forEach((e) => {
|
|
194
|
+
e.timer !== null && window.clearTimeout(e.timer);
|
|
195
|
+
}), this.elementImpressionStates.clear(), this.subscribers.clear(), this.logger.info("Impression tracking cleaned up");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
C as DotCMSImpressionTracker
|
|
200
|
+
};
|
|
@@ -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
|
+
};
|