@dotcms/analytics 1.2.0-next.9 → 1.2.1-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +261 -15
  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 +1 -1
  10. package/lib/core/plugin/enricher/dot-analytics.enricher.plugin.js +14 -14
  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 -37
  13. package/lib/core/plugin/identity/dot-analytics.identity.plugin.js +7 -7
  14. package/lib/core/plugin/identity/dot-analytics.identity.utils.d.ts +0 -16
  15. package/lib/core/{shared/dot-content-analytics.impression-tracker.d.ts → plugin/impression/dot-analytics.impression-tracker.d.ts} +2 -2
  16. package/lib/core/{shared/dot-content-analytics.impression-tracker.js → plugin/impression/dot-analytics.impression-tracker.js} +41 -57
  17. package/lib/core/plugin/impression/dot-analytics.impression.plugin.d.ts +4 -2
  18. package/lib/core/plugin/impression/dot-analytics.impression.plugin.js +13 -21
  19. package/lib/core/plugin/impression/dot-analytics.impression.utils.d.ts +0 -25
  20. package/lib/core/plugin/impression/dot-analytics.impression.utils.js +15 -38
  21. package/lib/core/plugin/{dot-analytics.plugin.d.ts → main/dot-analytics.plugin.d.ts} +2 -1
  22. package/lib/core/plugin/main/dot-analytics.plugin.js +129 -0
  23. package/lib/core/shared/constants/{dot-content-analytics.constants.d.ts → dot-analytics.constants.d.ts} +16 -0
  24. package/lib/core/shared/constants/dot-analytics.constants.js +53 -0
  25. package/lib/core/shared/constants/index.d.ts +1 -1
  26. package/lib/core/shared/dot-analytics.logger.d.ts +85 -0
  27. package/lib/core/shared/dot-analytics.logger.js +90 -0
  28. package/lib/core/shared/{dot-content-analytics.http.d.ts → http/dot-analytics.http.d.ts} +1 -1
  29. package/lib/core/shared/http/dot-analytics.http.js +34 -0
  30. package/lib/core/shared/models/event.model.d.ts +69 -2
  31. package/lib/core/shared/models/library.model.d.ts +31 -5
  32. package/lib/core/shared/queue/dot-analytics.queue.utils.js +44 -37
  33. package/lib/core/shared/{dot-content-analytics.utils.d.ts → utils/dot-analytics.utils.d.ts} +72 -4
  34. package/lib/core/shared/utils/dot-analytics.utils.js +202 -0
  35. package/lib/react/hook/useContentAnalytics.js +17 -11
  36. package/lib/react/hook/useRouterTracker.js +4 -4
  37. package/lib/react/internal/utils.js +1 -1
  38. package/package.json +7 -6
  39. package/lib/core/dot-content-analytics.js +0 -56
  40. package/lib/core/plugin/dot-analytics.plugin.js +0 -97
  41. package/lib/core/shared/constants/dot-content-analytics.constants.js +0 -48
  42. package/lib/core/shared/dot-content-analytics.http.js +0 -36
  43. package/lib/core/shared/dot-content-analytics.utils.js +0 -147
  44. /package/lib/core/{dot-content-analytics.d.ts → dot-analytics.content.d.ts} +0 -0
package/README.md CHANGED
@@ -79,6 +79,67 @@ pageView(customData?: Record<string, unknown>): void
79
79
  - **React**: In development (API may change)
80
80
  - Custom data is optional and gets attached to the pageview event under the `custom` property alongside all automatically captured data.
81
81
 
82
+ ### Conversion Tracking
83
+
84
+ The `conversion()` method tracks user conversions (purchases, downloads, sign-ups, etc.) from your application.
85
+
86
+ **⚠️ IMPORTANT: Conversion events are business events that should only be tracked after a successful action or completed goal.** Tracking conversions on clicks or attempts (before success) diminishes their value as conversion metrics. Only track conversions when:
87
+
88
+ - ✅ Purchase is completed and payment is confirmed
89
+ - ✅ Download is successfully completed
90
+ - ✅ Sign-up form is submitted and account is created
91
+ - ✅ Form submission is successful and data is saved
92
+ - ✅ Any business goal is actually achieved
93
+
94
+ **Method signature:**
95
+
96
+ ```typescript
97
+ conversion(name: string): void
98
+ conversion(name: string, options?: Record<string, unknown>): void
99
+ ```
100
+
101
+ **Usage examples:**
102
+
103
+ ```typescript
104
+ // Basic conversion tracking (after successful download)
105
+ analytics.conversion('download');
106
+
107
+ // Conversion with custom metadata (after successful purchase)
108
+ analytics.conversion('purchase', {
109
+ value: 99.99,
110
+ currency: 'USD',
111
+ category: 'ecommerce',
112
+ productId: 'SKU-12345'
113
+ });
114
+
115
+ // Conversion with additional context (after successful signup)
116
+ analytics.conversion('signup', {
117
+ source: 'homepage',
118
+ plan: 'premium'
119
+ });
120
+ ```
121
+
122
+ **Event payload structure:**
123
+
124
+ ```json
125
+ {
126
+ "event_type": "conversion",
127
+ "local_time": "2025-10-01T16:08:33-04:00",
128
+ "data": {
129
+ "conversion": { "name": "download" },
130
+ "page": { "url": "...", "title": "..." },
131
+ "custom": { "value": 99.99, "currency": "USD", "source": "homepage" }
132
+ }
133
+ }
134
+ ```
135
+
136
+ **Important:**
137
+
138
+ - `name` is required and identifies the conversion type
139
+ - All properties in `options` go into the `custom` object
140
+ - Page data (url, title) is automatically added by the SDK
141
+ - **Only track conversions after successful completion of business goals**
142
+
82
143
  ### Custom Events
83
144
 
84
145
  The `track()` method allows you to track any custom user action with a unique event name and optional properties.
@@ -91,7 +152,7 @@ track(eventName: string, properties?: Record<string, unknown>): void
91
152
 
92
153
  **Important:**
93
154
 
94
- - `eventName` cannot be `"pageview"` (reserved for page view tracking)
155
+ - `eventName` cannot be `"pageview"` or `"conversion"` (reserved for specific tracking methods)
95
156
  - `eventName` should be a descriptive string like `"button-click"`, `"form-submit"`, `"video-play"`, etc.
96
157
  - `properties` is optional and can contain any custom data relevant to the event
97
158
 
@@ -108,14 +169,15 @@ track(eventName: string, properties?: Record<string, unknown>): void
108
169
 
109
170
  ## ⚙️ Configuration Options
110
171
 
111
- | Option | Type | Required | Default | Description |
112
- | -------------- | --------- | -------- | ----------------------------------- | -------------------------------------- |
113
- | `siteAuth` | `string` | ✅ | - | Site auth from dotCMS Analytics app |
114
- | `server` | `string` | ✅ | - | Your dotCMS server URL |
115
- | `debug` | `boolean` | ❌ | `false` | Enable verbose logging |
116
- | `autoPageView` | `boolean` | ❌ | React: `true` / Standalone: `false` | Auto track page views on route changes |
117
- | `queueConfig` | `QueueConfig` | ❌ | See below | Event batching configuration |
118
- | `impressions` | `ImpressionConfig\|boolean` | ❌ | `false` | Content impression tracking (disabled by default) |
172
+ | Option | Type | Required | Default | Description |
173
+ | -------------- | --------------------------- | -------- | ----------------------------------- | ---------------------------------------------------------------- |
174
+ | `siteAuth` | `string` | ✅ | - | Site auth from dotCMS Analytics app |
175
+ | `server` | `string` | ✅ | - | Your dotCMS server URL |
176
+ | `debug` | `boolean` | ❌ | `false` | Enable verbose logging |
177
+ | `autoPageView` | `boolean` | ❌ | React: `true` / Standalone: `false` | Auto track page views on route changes |
178
+ | `queueConfig` | `QueueConfig` | ❌ | See below | Event batching configuration |
179
+ | `impressions` | `ImpressionConfig\|boolean` | ❌ | `false` | Content impression tracking (disabled by default) |
180
+ | `clicks` | `boolean` | ❌ | `false` | Content click tracking with 300ms throttle (disabled by default) |
119
181
 
120
182
  ### Queue Configuration
121
183
 
@@ -177,11 +239,11 @@ The `impressions` option controls automatic tracking of content visibility:
177
239
  - **`true`**: Enable tracking with default settings
178
240
  - **`ImpressionConfig` object**: Enable tracking with custom settings
179
241
 
180
- | Option | Type | Default | Description |
181
- | --------------------- | -------- | ------- | ------------------------------------------------ |
182
- | `visibilityThreshold` | `number` | `0.5` | Min percentage visible (0.0 to 1.0) |
183
- | `dwellMs` | `number` | `750` | Min time visible in milliseconds |
184
- | `maxNodes` | `number` | `1000` | Max elements to track (performance limit) |
242
+ | Option | Type | Default | Description |
243
+ | --------------------- | -------- | ------- | ----------------------------------------- |
244
+ | `visibilityThreshold` | `number` | `0.5` | Min percentage visible (0.0 to 1.0) |
245
+ | `dwellMs` | `number` | `750` | Min time visible in milliseconds |
246
+ | `maxNodes` | `number` | `1000` | Max elements to track (performance limit) |
185
247
 
186
248
  **How it works:**
187
249
 
@@ -227,6 +289,136 @@ const analytics = initializeContentAnalytics({
227
289
  });
228
290
  ```
229
291
 
292
+ ### Click Tracking Configuration
293
+
294
+ The `clicks` option controls automatic tracking of user interactions with content elements:
295
+
296
+ - **`false` or `undefined` (default)**: Click tracking disabled
297
+ - **`true`**: Enable tracking with default settings (300ms throttle)
298
+
299
+ **How it works:**
300
+
301
+ - ✅ Tracks clicks on `<a>` and `<button>` elements within contentlets
302
+ - ✅ Contentlets must be marked with `dotcms-analytics-contentlet` class and `data-dot-analytics-*` attributes
303
+ - ✅ Captures semantic attributes (`href`, `aria-label`, `data-*`) and excludes CSS classes
304
+ - ✅ Throttles rapid clicks to prevent duplicate tracking (300ms fixed)
305
+ - ✅ One click event per interaction
306
+ - ✅ Respects user consent settings
307
+ - ✅ Automatically disabled in dotCMS editor mode
308
+
309
+ **Captured Data:**
310
+
311
+ For each click, the SDK captures:
312
+
313
+ - **Content Info**: `identifier`, `inode`, `title`, `content_type`
314
+ - **Element Info**:
315
+ - `text` - Button/link text (truncated to 100 chars)
316
+ - `type` - Element type (`a` or `button`)
317
+ - `id` - Element ID (required by backend, empty string if not present)
318
+ - `class` - Element CSS classes (required by backend, empty string if not present)
319
+ - `href` - Link destination as written in HTML (e.g., `/signup` not `http://...`, only for `<a>`, empty string for buttons)
320
+ - `attributes` - Additional useful attributes (see below)
321
+ - **Position Info**:
322
+ - `viewport_offset_pct` - Position relative to viewport (0-100%)
323
+ - `dom_index` - Element position in DOM
324
+
325
+ **Attributes Array:**
326
+
327
+ > **Note**: The `attributes` field is formatted as an array of `'key:value'` strings (e.g., `['data-category:primary-cta', 'aria-label:Sign up']`) for efficient serialization and backend parsing.
328
+
329
+ The `attributes` array captures additional semantic data in `'key:value'` string format:
330
+
331
+ ✅ **Included** (semantic/analytics value):
332
+
333
+ - `data-*` - Custom data attributes (e.g., `'data-category:primary-cta'`)
334
+ - `aria-*` - Accessibility attributes (e.g., `'aria-label:Sign up now'`)
335
+ - `title` - Element title
336
+ - `target` - Link target (e.g., `'target:_blank'`)
337
+ - Any other standard HTML attributes
338
+
339
+ ❌ **Excluded** (to avoid duplication):
340
+
341
+ - `class` - Already captured as top-level property
342
+ - `id` - Already captured as top-level property
343
+ - `href` - Already captured as top-level property
344
+ - `data-dot-analytics-*` - Internal SDK attributes
345
+
346
+ **Example: Enable click tracking**
347
+
348
+ ```javascript
349
+ const analytics = initializeContentAnalytics({
350
+ siteAuth: 'abc123',
351
+ server: 'https://your-dotcms.com',
352
+ clicks: true // Enable with 300ms throttle (fixed)
353
+ });
354
+ ```
355
+
356
+ **Example: Adding Custom Analytics Metadata**
357
+
358
+ Use `data-*` attributes to enrich click tracking with custom metadata:
359
+
360
+ ```html
361
+ <!-- Primary CTA with category -->
362
+ <a
363
+ href="/signup"
364
+ id="cta-signup"
365
+ data-category="primary-cta"
366
+ data-campaign="summer-sale"
367
+ aria-label="Sign up for free trial">
368
+ Start Free Trial →
369
+ </a>
370
+
371
+ <!-- Product link with metadata -->
372
+ <a href="/products/123" data-product-id="123" data-product-name="Premium Plan" data-price="29.99">
373
+ View Product
374
+ </a>
375
+
376
+ <!-- Button with custom tracking -->
377
+ <button data-action="download" data-file-type="pdf" data-category="lead-magnet">
378
+ Download Whitepaper
379
+ </button>
380
+ ```
381
+
382
+ **Resulting Click Event:**
383
+
384
+ ```json
385
+ {
386
+ "content": {
387
+ "identifier": "abc123",
388
+ "inode": "xyz789",
389
+ "title": "Product Page",
390
+ "content_type": "Page"
391
+ },
392
+ "element": {
393
+ "text": "Start Free Trial →",
394
+ "type": "a",
395
+ "id": "cta-signup",
396
+ "class": "btn btn-primary text-white",
397
+ "href": "/signup",
398
+ "attributes": [
399
+ "data-category:primary-cta",
400
+ "data-campaign:summer-sale",
401
+ "aria-label:Sign up for free trial",
402
+ "target:_blank"
403
+ ]
404
+ },
405
+ "position": {
406
+ "viewport_offset_pct": 45.2,
407
+ "dom_index": 2
408
+ }
409
+ }
410
+ ```
411
+
412
+ **Example: Disable tracking**
413
+
414
+ ```javascript
415
+ const analytics = initializeContentAnalytics({
416
+ siteAuth: 'abc123',
417
+ server: 'https://your-dotcms.com',
418
+ clicks: false // Explicitly disabled (also default if omitted)
419
+ });
420
+ ```
421
+
230
422
  ## 🛠️ Usage Examples
231
423
 
232
424
  ### Vanilla JavaScript
@@ -356,10 +548,18 @@ interface DotCMSAnalytics {
356
548
 
357
549
  /**
358
550
  * Track a custom event
359
- * @param eventName - Name of the custom event (cannot be "pageview")
551
+ * @param eventName - Name of the custom event (cannot be "pageview" or "conversion")
360
552
  * @param properties - Optional object with event-specific properties
361
553
  */
362
554
  track: (eventName: string, properties?: Record<string, unknown>) => void;
555
+
556
+ /**
557
+ * Track a conversion event (purchase, download, sign-up, etc.)
558
+ * ⚠️ IMPORTANT: Only track conversions after successful completion of business goals
559
+ * @param name - Name of the conversion (e.g., "purchase", "download", "signup")
560
+ * @param options - Optional object with conversion metadata (all properties go into custom object)
561
+ */
562
+ conversion: (name: string, options?: Record<string, unknown>) => void;
363
563
  }
364
564
  ```
365
565
 
@@ -449,6 +649,52 @@ When you call `track(eventName, properties)`, the following structure is sent:
449
649
  }
450
650
  ```
451
651
 
652
+ ### Conversion Event Structure
653
+
654
+ When you call `conversion(name, options)`, the following structure is sent:
655
+
656
+ ```typescript
657
+ {
658
+ context: {
659
+ site_key: string; // Your site key
660
+ session_id: string; // Current session ID
661
+ user_id: string; // Anonymous user ID
662
+ device: { // 🤖 AUTOMATIC - Device & Browser Info
663
+ screen_resolution: string; // Screen size
664
+ language: string; // Browser language
665
+ viewport_width: string; // Viewport width
666
+ viewport_height: string; // Viewport height
667
+ }
668
+ },
669
+ events: [{
670
+ event_type: "conversion",
671
+ local_time: string, // ISO 8601 timestamp
672
+ data: {
673
+ conversion: { // 🤖 AUTOMATIC - Conversion Info
674
+ name: string; // Your conversion name
675
+ },
676
+ page: { // 🤖 AUTOMATIC - Page Information
677
+ url: string; // Full URL
678
+ title: string; // Page title
679
+ },
680
+ custom?: { // 👤 YOUR DATA (optional)
681
+ // All properties from options parameter
682
+ value?: number;
683
+ currency?: string;
684
+ category?: string;
685
+ // ... any other custom properties
686
+ }
687
+ }
688
+ }]
689
+ }
690
+ ```
691
+
692
+ **Key Points:**
693
+
694
+ - 🤖 Conversion name and page data are captured **automatically**
695
+ - 👤 All properties in `options` go into the `custom` object
696
+ - ⚠️ **Only track conversions after successful completion of business goals**
697
+
452
698
  ## Under the Hood
453
699
 
454
700
  ### Storage Keys
@@ -0,0 +1,84 @@
1
+ import { Analytics as l } from "analytics";
2
+ import { ANALYTICS_WINDOWS_ACTIVE_KEY as a, ANALYTICS_WINDOWS_CLEANUP_KEY as d } from "../../uve/src/internal/constants.js";
3
+ import { dotAnalyticsClickPlugin as y } from "./plugin/click/dot-analytics.click.plugin.js";
4
+ import { dotAnalyticsEnricherPlugin as m } from "./plugin/enricher/dot-analytics.enricher.plugin.js";
5
+ import { dotAnalyticsIdentityPlugin as u } from "./plugin/identity/dot-analytics.identity.plugin.js";
6
+ import { dotAnalyticsImpressionPlugin as p } from "./plugin/impression/dot-analytics.impression.plugin.js";
7
+ import { dotAnalytics as A } from "./plugin/main/dot-analytics.plugin.js";
8
+ import { DotCMSPredefinedEventType as f } from "./shared/constants/dot-analytics.constants.js";
9
+ import { validateAnalyticsConfig as C, getEnhancedTrackingPlugins as w } from "./shared/utils/dot-analytics.utils.js";
10
+ import { cleanupActivityTracking as g } from "./plugin/identity/dot-analytics.identity.activity-tracker.js";
11
+ const _ = (t) => {
12
+ const e = C(t);
13
+ if (e)
14
+ return console.error(
15
+ `DotCMS Analytics [Core]: Missing ${e.join(" and ")} in configuration`
16
+ ), typeof window < "u" && (window[a] = !1), null;
17
+ const s = w(
18
+ t,
19
+ p,
20
+ y
21
+ ), i = l({
22
+ app: "dotAnalytics",
23
+ debug: t.debug,
24
+ plugins: [
25
+ u(t),
26
+ // Inject identity context
27
+ ...s,
28
+ //Track content impressions & clicks (conditionally loaded)
29
+ m(),
30
+ // Enrich and clean payload with page, device, utm data and custom data
31
+ A(t)
32
+ // Send events to server
33
+ ]
34
+ }), r = () => g();
35
+ return typeof window < "u" && (window.addEventListener("beforeunload", r), window[d] = r, window[a] = !0, window.dispatchEvent(new CustomEvent("dotcms:analytics:ready"))), {
36
+ /**
37
+ * Track a page view.
38
+ * Session activity is automatically updated by the identity plugin.
39
+ * @param payload - Optional custom data to include with the page view (any valid JSON object)
40
+ */
41
+ pageView: (n = {}) => {
42
+ if (!i) {
43
+ console.warn("DotCMS Analytics [Core]: Analytics instance not initialized");
44
+ return;
45
+ }
46
+ i.page(n);
47
+ },
48
+ /**
49
+ * Track a custom event.
50
+ * @param eventName - The name of the event to track
51
+ * @param payload - Custom data to include with the event (any valid JSON object)
52
+ */
53
+ track: (n, o = {}) => {
54
+ if (!i) {
55
+ console.warn("DotCMS Analytics [Core]: Analytics instance not initialized");
56
+ return;
57
+ }
58
+ i.track(n, o);
59
+ },
60
+ /**
61
+ * Track a conversion event.
62
+ * @param name - Name of the conversion (e.g., 'purchase', 'download', 'signup')
63
+ * @param options - Optional custom data
64
+ */
65
+ conversion: (n, o = {}) => {
66
+ if (!i) {
67
+ console.warn("DotCMS Analytics [Core]: Analytics instance not initialized");
68
+ return;
69
+ }
70
+ if (!n || n.trim() === "") {
71
+ console.warn("DotCMS Analytics [Core]: Conversion name cannot be empty");
72
+ return;
73
+ }
74
+ const c = {
75
+ name: n,
76
+ ...Object.keys(o).length > 0 && { custom: o }
77
+ };
78
+ i.track(f.CONVERSION, c);
79
+ }
80
+ };
81
+ };
82
+ export {
83
+ _ as initializeContentAnalytics
84
+ };
@@ -0,0 +1,108 @@
1
+ import { DotCMSAnalyticsConfig, DotCMSContentClickPayload } from '../../shared/models';
2
+ /** Callback function for click events */
3
+ export type ClickCallback = (eventName: string, payload: DotCMSContentClickPayload) => void;
4
+ /** Subscription object with unsubscribe method */
5
+ export interface ClickSubscription {
6
+ unsubscribe: () => void;
7
+ }
8
+ /**
9
+ * Tracks content clicks using event listeners on contentlet containers.
10
+ * Detects clicks on <a> and <button> elements inside contentlets and fires events.
11
+ *
12
+ * Features:
13
+ * - Attaches event listeners to contentlet containers
14
+ * - Tracks clicks on anchor and button elements only
15
+ * - Uses MutationObserver to detect dynamically added content
16
+ * - Throttles rapid clicks to prevent duplicates (300ms)
17
+ * - Subscription-based event system for decoupling
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const tracker = new DotCMSClickTracker(config);
22
+ * const subscription = tracker.onClick((eventName, payload) => {
23
+ * console.log('Click detected:', payload);
24
+ * });
25
+ * tracker.initialize();
26
+ * // Later: subscription.unsubscribe();
27
+ * ```
28
+ */
29
+ export declare class DotCMSClickTracker {
30
+ private config;
31
+ private mutationObserver;
32
+ private lastClickTime;
33
+ private logger;
34
+ private subscribers;
35
+ private trackedElements;
36
+ private elementHandlers;
37
+ constructor(config: DotCMSAnalyticsConfig);
38
+ /**
39
+ * Subscribe to click events
40
+ * @param callback - Function called when click is detected
41
+ * @returns Subscription object with unsubscribe method
42
+ */
43
+ onClick(callback: ClickCallback): ClickSubscription;
44
+ /**
45
+ * Notifies all subscribers of a click event
46
+ * @param eventName - Name of the event (e.g., 'content_click')
47
+ * @param payload - Click event payload with content and element data
48
+ */
49
+ private notifySubscribers;
50
+ /**
51
+ * Initialize click tracking system
52
+ *
53
+ * Performs the following:
54
+ * - Validates browser environment
55
+ * - Scans for existing contentlets after a delay (100ms)
56
+ * - Sets up MutationObserver for dynamic content
57
+ *
58
+ * The delay allows React/Next.js to finish initial rendering
59
+ * before attaching listeners.
60
+ */
61
+ initialize(): void;
62
+ /**
63
+ * Attach click listener to a contentlet container
64
+ *
65
+ * Skips if element already has a listener attached.
66
+ * The listener delegates to handleContentletClick which:
67
+ * - Finds clicked anchor/button elements
68
+ * - Extracts contentlet and element data
69
+ * - Applies throttling (300ms)
70
+ * - Notifies subscribers
71
+ *
72
+ * @param element - Contentlet container element to track
73
+ */
74
+ private attachClickListener;
75
+ /**
76
+ * Find and attach listeners to all contentlet elements in the DOM
77
+ *
78
+ * Scans the entire document for elements with the
79
+ * `.dotcms-analytics-contentlet` class and attaches click
80
+ * listeners if not already tracked.
81
+ *
82
+ * Called during initialization and whenever DOM mutations are detected.
83
+ */
84
+ private findAndAttachListeners;
85
+ /**
86
+ * Initialize MutationObserver to detect new contentlet containers
87
+ * Uses same simple strategy as impression tracker - no complex filtering
88
+ */
89
+ private initializeMutationObserver;
90
+ /**
91
+ * Remove all click listeners from tracked contentlets
92
+ *
93
+ * Iterates through all contentlet elements and removes their
94
+ * click event handlers, cleaning up WeakMap references.
95
+ */
96
+ private removeAllListeners;
97
+ /**
98
+ * Cleanup all resources used by the click tracker
99
+ *
100
+ * Performs:
101
+ * - Removes all event listeners from contentlets
102
+ * - Disconnects MutationObserver
103
+ * - Clears internal references
104
+ *
105
+ * Should be called when the plugin is disabled or on page unload.
106
+ */
107
+ cleanup(): void;
108
+ }
@@ -0,0 +1,144 @@
1
+ import { handleContentletClick as c } from "./dot-analytics.click.utils.js";
2
+ import { DEFAULT_CLICK_THROTTLE_MS as d } from "../../shared/constants/dot-analytics.constants.js";
3
+ import { createPluginLogger as h, isBrowser as l, INITIAL_SCAN_DELAY_MS as g, findContentlets as r, createContentletObserver as u } from "../../shared/utils/dot-analytics.utils.js";
4
+ class C {
5
+ constructor(t) {
6
+ this.config = t, this.mutationObserver = null, this.lastClickTime = { value: 0 }, this.subscribers = /* @__PURE__ */ new Set(), this.trackedElements = /* @__PURE__ */ new WeakSet(), this.elementHandlers = /* @__PURE__ */ new WeakMap(), this.logger = h("Click", t);
7
+ }
8
+ /**
9
+ * Subscribe to click events
10
+ * @param callback - Function called when click is detected
11
+ * @returns Subscription object with unsubscribe method
12
+ */
13
+ onClick(t) {
14
+ return this.subscribers.add(t), {
15
+ unsubscribe: () => {
16
+ this.subscribers.delete(t);
17
+ }
18
+ };
19
+ }
20
+ /**
21
+ * Notifies all subscribers of a click event
22
+ * @param eventName - Name of the event (e.g., 'content_click')
23
+ * @param payload - Click event payload with content and element data
24
+ */
25
+ notifySubscribers(t, e) {
26
+ this.subscribers.forEach((i) => i(t, e));
27
+ }
28
+ /**
29
+ * Initialize click tracking system
30
+ *
31
+ * Performs the following:
32
+ * - Validates browser environment
33
+ * - Scans for existing contentlets after a delay (100ms)
34
+ * - Sets up MutationObserver for dynamic content
35
+ *
36
+ * The delay allows React/Next.js to finish initial rendering
37
+ * before attaching listeners.
38
+ */
39
+ initialize() {
40
+ if (!l()) {
41
+ this.logger.warn("No document, skipping");
42
+ return;
43
+ }
44
+ this.logger.debug("Plugin initializing"), typeof window < "u" && setTimeout(() => {
45
+ this.logger.debug("Running initial scan after timeout..."), this.findAndAttachListeners();
46
+ }, g), this.initializeMutationObserver(), this.logger.info("Plugin initialized");
47
+ }
48
+ /**
49
+ * Attach click listener to a contentlet container
50
+ *
51
+ * Skips if element already has a listener attached.
52
+ * The listener delegates to handleContentletClick which:
53
+ * - Finds clicked anchor/button elements
54
+ * - Extracts contentlet and element data
55
+ * - Applies throttling (300ms)
56
+ * - Notifies subscribers
57
+ *
58
+ * @param element - Contentlet container element to track
59
+ */
60
+ attachClickListener(t) {
61
+ if (this.trackedElements.has(t)) {
62
+ const n = t.dataset.dotAnalyticsIdentifier || "unknown";
63
+ this.logger.debug(`Element ${n} already has listener, skipping`);
64
+ return;
65
+ }
66
+ if (!t.dataset.dotAnalyticsDomIndex) {
67
+ const n = r();
68
+ t.dataset.dotAnalyticsDomIndex = String(n.indexOf(t));
69
+ }
70
+ const e = (n) => {
71
+ this.logger.debug("Click handler triggered on contentlet"), c(
72
+ n,
73
+ t,
74
+ (a, s) => {
75
+ const o = Date.now();
76
+ o - this.lastClickTime.value < d || (this.lastClickTime.value = o, this.notifySubscribers(a, s), this.logger.info(
77
+ `Fired click event for ${s.content.identifier}`,
78
+ s
79
+ ));
80
+ },
81
+ this.logger
82
+ );
83
+ };
84
+ t.addEventListener("click", e), this.trackedElements.add(t), this.elementHandlers.set(t, e);
85
+ const i = t.dataset.dotAnalyticsIdentifier || "unknown";
86
+ this.logger.log(`Attached listener to contentlet ${i}`, t);
87
+ }
88
+ /**
89
+ * Find and attach listeners to all contentlet elements in the DOM
90
+ *
91
+ * Scans the entire document for elements with the
92
+ * `.dotcms-analytics-contentlet` class and attaches click
93
+ * listeners if not already tracked.
94
+ *
95
+ * Called during initialization and whenever DOM mutations are detected.
96
+ */
97
+ findAndAttachListeners() {
98
+ this.logger.debug("findAndAttachListeners called");
99
+ const t = r();
100
+ this.logger.debug(`Scanning... found ${t.length} contentlets`);
101
+ let e = 0;
102
+ t.forEach((i) => {
103
+ const n = !this.trackedElements.has(i);
104
+ this.attachClickListener(i), n && this.trackedElements.has(i) && e++;
105
+ }), e > 0 && this.logger.info(`Attached ${e} new click listeners`);
106
+ }
107
+ /**
108
+ * Initialize MutationObserver to detect new contentlet containers
109
+ * Uses same simple strategy as impression tracker - no complex filtering
110
+ */
111
+ initializeMutationObserver() {
112
+ l() && (this.mutationObserver = u(() => {
113
+ this.findAndAttachListeners();
114
+ }), this.logger.info("MutationObserver enabled for click tracking"));
115
+ }
116
+ /**
117
+ * Remove all click listeners from tracked contentlets
118
+ *
119
+ * Iterates through all contentlet elements and removes their
120
+ * click event handlers, cleaning up WeakMap references.
121
+ */
122
+ removeAllListeners() {
123
+ r().forEach((e) => {
124
+ const i = this.elementHandlers.get(e);
125
+ i && (e.removeEventListener("click", i), this.elementHandlers.delete(e));
126
+ });
127
+ }
128
+ /**
129
+ * Cleanup all resources used by the click tracker
130
+ *
131
+ * Performs:
132
+ * - Removes all event listeners from contentlets
133
+ * - Disconnects MutationObserver
134
+ * - Clears internal references
135
+ *
136
+ * Should be called when the plugin is disabled or on page unload.
137
+ */
138
+ cleanup() {
139
+ this.removeAllListeners(), this.mutationObserver && (this.mutationObserver.disconnect(), this.mutationObserver = null), this.logger.info("Click tracking cleaned up");
140
+ }
141
+ }
142
+ export {
143
+ C as DotCMSClickTracker
144
+ };