@streamscloud/streams-analytics-collector 1.0.10 → 1.0.12

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.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ViewportVisibilityTracker
3
+ *
4
+ * Utility for tracking when DOM elements (e.g., stream tiles) become visible in the viewport.
5
+ * Fires impression events only once per unique element (by streamId) per session/visit.
6
+ * Uses IntersectionObserver for efficient observation, with a fallback to manual visibility checks.
7
+ *
8
+ * Usage:
9
+ * ViewportVisibilityTracker.registerTile(element, streamId, callback?);
10
+ *
11
+ * - The element must have a unique streamId (e.g., from data-stream-id attribute).
12
+ * - The impression event is fired only once per streamId per session.
13
+ * - Handles rapid scrolling, re-renders, and element replacement.
14
+ * - You can provide a custom callback to be called when the element becomes visible.
15
+ */
16
+ export declare class ViewportVisibilityTracker {
17
+ private static trackedStreamIds;
18
+ private static observedElements;
19
+ private static callbacks;
20
+ private static observer;
21
+ /**
22
+ * Register a tile element for visibility tracking.
23
+ * @param el The DOM element to observe
24
+ * @param streamId The unique stream ID for this tile
25
+ * @param callback Optional callback to call when the element becomes visible (defaults to AppEventsTracker.trackStreamTileImpression)
26
+ */
27
+ static registerTile(el: HTMLElement, streamId: string, callback?: (streamId: string) => void): void;
28
+ /**
29
+ * Internal: Get or create the IntersectionObserver instance
30
+ */
31
+ private static getObserver;
32
+ /**
33
+ * Internal: IntersectionObserver callback
34
+ */
35
+ private static handleIntersections;
36
+ /**
37
+ * Fallback: Check if an element is in the viewport (at least 50% visible)
38
+ * @param el The DOM element
39
+ * @returns true if at least 50% of the element is visible
40
+ */
41
+ static isElementInViewport(el: HTMLElement): boolean;
42
+ /**
43
+ * Reset tracked state (for testing or new session)
44
+ */
45
+ static reset(): void;
46
+ }
@@ -0,0 +1,117 @@
1
+ import { AppEventsTracker } from './app-events-tracker.js';
2
+
3
+ /**
4
+ * ViewportVisibilityTracker
5
+ *
6
+ * Utility for tracking when DOM elements (e.g., stream tiles) become visible in the viewport.
7
+ * Fires impression events only once per unique element (by streamId) per session/visit.
8
+ * Uses IntersectionObserver for efficient observation, with a fallback to manual visibility checks.
9
+ *
10
+ * Usage:
11
+ * ViewportVisibilityTracker.registerTile(element, streamId, callback?);
12
+ *
13
+ * - The element must have a unique streamId (e.g., from data-stream-id attribute).
14
+ * - The impression event is fired only once per streamId per session.
15
+ * - Handles rapid scrolling, re-renders, and element replacement.
16
+ * - You can provide a custom callback to be called when the element becomes visible.
17
+ */
18
+ class ViewportVisibilityTracker {
19
+ // Set of streamIds that have already been tracked for impression
20
+ static trackedStreamIds = new Set();
21
+ // Map from streamId to the currently observed element
22
+ static observedElements = new Map();
23
+ // Map from streamId to the callback to call when visible
24
+ static callbacks = new Map();
25
+ // Singleton IntersectionObserver instance
26
+ static observer = null;
27
+ /**
28
+ * Register a tile element for visibility tracking.
29
+ * @param el The DOM element to observe
30
+ * @param streamId The unique stream ID for this tile
31
+ * @param callback Optional callback to call when the element becomes visible (defaults to AppEventsTracker.trackStreamTileImpression)
32
+ */
33
+ static registerTile(el, streamId, callback = AppEventsTracker.trackStreamTileImpression.bind(AppEventsTracker)) {
34
+ if (!el || !streamId)
35
+ return;
36
+ // If already tracked, do nothing
37
+ if (this.trackedStreamIds.has(streamId))
38
+ return;
39
+ // If an element for this streamId is already being observed, unobserve it (handles re-renders)
40
+ const prevEl = this.observedElements.get(streamId);
41
+ if (prevEl && prevEl !== el) {
42
+ this.getObserver().unobserve(prevEl);
43
+ }
44
+ this.observedElements.set(streamId, el);
45
+ this.callbacks.set(streamId, callback);
46
+ this.getObserver().observe(el);
47
+ }
48
+ /**
49
+ * Internal: Get or create the IntersectionObserver instance
50
+ */
51
+ static getObserver() {
52
+ if (!this.observer) {
53
+ this.observer = new window.IntersectionObserver(this.handleIntersections.bind(this), {
54
+ threshold: 0.5, // At least 50% of the element must be visible
55
+ });
56
+ }
57
+ return this.observer;
58
+ }
59
+ /**
60
+ * Internal: IntersectionObserver callback
61
+ */
62
+ static handleIntersections(entries) {
63
+ for (const entry of entries) {
64
+ const el = entry.target;
65
+ // Try to get streamId from map or from data attribute
66
+ let streamId = Array.from(this.observedElements.entries()).find(([, v]) => v === el)?.[0];
67
+ if (!streamId) {
68
+ streamId = el.getAttribute('data-stream-id') || undefined;
69
+ }
70
+ if (!streamId)
71
+ continue;
72
+ if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
73
+ // Only fire once per streamId per session
74
+ if (!this.trackedStreamIds.has(streamId)) {
75
+ this.trackedStreamIds.add(streamId);
76
+ const callback = this.callbacks.get(streamId);
77
+ if (callback) {
78
+ callback(streamId);
79
+ }
80
+ }
81
+ // Once tracked, stop observing this element
82
+ this.getObserver().unobserve(el);
83
+ this.observedElements.delete(streamId);
84
+ this.callbacks.delete(streamId);
85
+ }
86
+ }
87
+ }
88
+ /**
89
+ * Fallback: Check if an element is in the viewport (at least 50% visible)
90
+ * @param el The DOM element
91
+ * @returns true if at least 50% of the element is visible
92
+ */
93
+ static isElementInViewport(el) {
94
+ if (!el)
95
+ return false;
96
+ const rect = el.getBoundingClientRect();
97
+ const windowHeight = (window.innerHeight || document.documentElement.clientHeight);
98
+ const windowWidth = (window.innerWidth || document.documentElement.clientWidth);
99
+ const vertInView = (rect.top <= windowHeight * 0.5) && ((rect.top + rect.height * 0.5) >= 0);
100
+ const horInView = (rect.left <= windowWidth * 0.5) && ((rect.left + rect.width * 0.5) >= 0);
101
+ return vertInView && horInView;
102
+ }
103
+ /**
104
+ * Reset tracked state (for testing or new session)
105
+ */
106
+ static reset() {
107
+ this.trackedStreamIds.clear();
108
+ this.observedElements.clear();
109
+ this.callbacks.clear();
110
+ if (this.observer) {
111
+ this.observer.disconnect();
112
+ this.observer = null;
113
+ }
114
+ }
115
+ }
116
+
117
+ export { ViewportVisibilityTracker };
@@ -66,10 +66,19 @@ export declare class AppEventsTracker {
66
66
  */
67
67
  static trackCommunityMessageOpened(messageId: string, status: string): void;
68
68
  /**
69
- * Track when a stream tile is shown to the user
69
+ * Track when a stream tile is shown to the user (basic, immediate call).
70
70
  * @param streamId - The ID of the stream
71
71
  */
72
72
  static trackStreamTileImpression(streamId: string): void;
73
+ /**
74
+ * Track when a stream tile is shown to the user, using viewport visibility tracking.
75
+ * The impression event will only be fired if the element becomes visible (at least 50% in viewport).
76
+ * The event is only fired once per streamId per session/visit.
77
+ *
78
+ * @param el - The DOM element to observe (e.g., the stream tile)
79
+ * @param streamId - The unique stream ID for this tile
80
+ */
81
+ static trackStreamTileImpressionWithVisibility(el: HTMLElement, streamId: string): void;
73
82
  /**
74
83
  * Track when a stream tile is clicked
75
84
  * @param streamId - The ID of the stream
@@ -111,6 +120,25 @@ export declare class AppEventsTracker {
111
120
  * @deprecated Consider using trackShortVideoProgress and reportPageVideoViews for better tracking of multiple videos
112
121
  */
113
122
  static trackClosed(postId: string, streamId: string): void;
123
+ /**
124
+ * Track when an ad is shown to the user
125
+ * @param adId - The ID of the ad
126
+ */
127
+ static trackAdImpression(adId: string): void;
128
+ /**
129
+ * Track when an ad is clicked by the user
130
+ * @param adId - The ID of the ad
131
+ */
132
+ static trackAdClick(adId: string): void;
133
+ /**
134
+ * Track when an ad is shown to the user, using viewport visibility tracking.
135
+ * The impression event will only be fired if the element becomes visible (at least 50% in viewport).
136
+ * The event is only fired once per adId per session/visit.
137
+ *
138
+ * @param el - The DOM element to observe (e.g., the ad element)
139
+ * @param adId - The unique ad ID for this element
140
+ */
141
+ static trackAdImpressionWithVisibility(el: HTMLElement, adId: string): void;
114
142
  /**
115
143
  * Report an app event to the API
116
144
  * @private
@@ -1,5 +1,6 @@
1
1
  import { AppEventType, CommunityMessageStatus } from './types.js';
2
2
  import AnalyticsQuery from '../analytics.graphql.js';
3
+ import { ViewportVisibilityTracker } from './ViewportVisibilityTracker.js';
3
4
 
4
5
  /**
5
6
  * AppEventsTracker is a utility class for tracking various user events in StreamsCloud applications
@@ -114,12 +115,28 @@ class AppEventsTracker {
114
115
  }
115
116
  }
116
117
  /**
117
- * Track when a stream tile is shown to the user
118
+ * Track when a stream tile is shown to the user (basic, immediate call).
118
119
  * @param streamId - The ID of the stream
119
120
  */
120
121
  static trackStreamTileImpression(streamId) {
121
122
  this.reportAppEvent(streamId, AppEventType.StreamTileImpression);
122
123
  }
124
+ /**
125
+ * Track when a stream tile is shown to the user, using viewport visibility tracking.
126
+ * The impression event will only be fired if the element becomes visible (at least 50% in viewport).
127
+ * The event is only fired once per streamId per session/visit.
128
+ *
129
+ * @param el - The DOM element to observe (e.g., the stream tile)
130
+ * @param streamId - The unique stream ID for this tile
131
+ */
132
+ static trackStreamTileImpressionWithVisibility(el, streamId) {
133
+ if (!el || !streamId)
134
+ return;
135
+ ViewportVisibilityTracker.registerTile(el, streamId, (sid) => {
136
+ this.reportAppEvent(sid, AppEventType.StreamTileImpression);
137
+ });
138
+ // The ViewportVisibilityTracker will call the callback when visible
139
+ }
123
140
  /**
124
141
  * Track when a stream tile is clicked
125
142
  * @param streamId - The ID of the stream
@@ -186,6 +203,36 @@ class AppEventsTracker {
186
203
  this.reported.splice(postIndex, 1);
187
204
  }
188
205
  }
206
+ /**
207
+ * Track when an ad is shown to the user
208
+ * @param adId - The ID of the ad
209
+ */
210
+ static trackAdImpression(adId) {
211
+ this.reportAppEvent(adId, AppEventType.AdImpression);
212
+ }
213
+ /**
214
+ * Track when an ad is clicked by the user
215
+ * @param adId - The ID of the ad
216
+ */
217
+ static trackAdClick(adId) {
218
+ this.reportAppEvent(adId, AppEventType.AdClick);
219
+ }
220
+ /**
221
+ * Track when an ad is shown to the user, using viewport visibility tracking.
222
+ * The impression event will only be fired if the element becomes visible (at least 50% in viewport).
223
+ * The event is only fired once per adId per session/visit.
224
+ *
225
+ * @param el - The DOM element to observe (e.g., the ad element)
226
+ * @param adId - The unique ad ID for this element
227
+ */
228
+ static trackAdImpressionWithVisibility(el, adId) {
229
+ if (!el || !adId)
230
+ return;
231
+ ViewportVisibilityTracker.registerTile(el, adId, (aid) => {
232
+ this.reportAppEvent(aid, AppEventType.AdImpression);
233
+ });
234
+ // The ViewportVisibilityTracker will call the callback when visible
235
+ }
189
236
  /**
190
237
  * Report an app event to the API
191
238
  * @private
@@ -1,2 +1,3 @@
1
1
  export { AppEventsTracker } from './app-events-tracker';
2
2
  export { AppEventType, CommunityMessageStatus, type TrackAppEventInput } from './types';
3
+ export * from './ViewportVisibilityTracker';
@@ -1,2 +1,3 @@
1
1
  export { AppEventsTracker } from './app-events-tracker.js';
2
2
  export { AppEventType, CommunityMessageStatus } from './types.js';
3
+ export { ViewportVisibilityTracker } from './ViewportVisibilityTracker.js';
@@ -8,7 +8,9 @@ export declare enum AppEventType {
8
8
  StreamEngagementTime = "STREAM_ENGAGEMENT_TIME",
9
9
  StreamScrollDepth = "STREAM_SCROLL_DEPTH",
10
10
  StreamPageView = "STREAM_PAGE_VIEW",
11
- StreamProductClick = "STREAM_PRODUCT_CLICK"
11
+ StreamProductClick = "STREAM_PRODUCT_CLICK",
12
+ AdImpression = "AD_IMPRESSION",
13
+ AdClick = "AD_CLICK"
12
14
  }
13
15
  export declare enum CommunityMessageStatus {
14
16
  Sent = "SENT",
@@ -10,6 +10,8 @@ var AppEventType;
10
10
  AppEventType["StreamScrollDepth"] = "STREAM_SCROLL_DEPTH";
11
11
  AppEventType["StreamPageView"] = "STREAM_PAGE_VIEW";
12
12
  AppEventType["StreamProductClick"] = "STREAM_PRODUCT_CLICK";
13
+ AppEventType["AdImpression"] = "AD_IMPRESSION";
14
+ AppEventType["AdClick"] = "AD_CLICK";
13
15
  })(AppEventType || (AppEventType = {}));
14
16
  var CommunityMessageStatus;
15
17
  (function (CommunityMessageStatus) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/streams-analytics-collector",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",