@schibsted/pulse-sdk 8.0.0-rc.6 → 8.0.0-rc.7
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/dist/cjs/Tracker.js +89 -26
- package/dist/cjs/tracker-proxy/consts.js +1 -0
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/visibility-observer/index.js +9 -7
- package/dist/cjs/wrapper/types/event.js +5 -0
- package/dist/ejs/Tracker.js +90 -27
- package/dist/ejs/tracker-proxy/consts.js +1 -0
- package/dist/ejs/version.js +1 -1
- package/dist/ejs/visibility-observer/index.js +9 -7
- package/dist/ejs/wrapper/types/event.js +4 -1
- package/dist/types/Tracker.d.ts +39 -3
- package/dist/types/tracker-proxy/consts.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/visibility-observer/index.d.ts +6 -1
- package/dist/types/wrapper/types/event.d.ts +3 -1
- package/node_modules/@schibsted/tpaas-event-builder/dist/cjs/builders.js +5 -1
- package/node_modules/@schibsted/tpaas-event-builder/dist/cjs/guards.js +7 -0
- package/node_modules/@schibsted/tpaas-event-builder/dist/cjs/index.js +3 -0
- package/node_modules/@schibsted/tpaas-event-builder/dist/ejs/builders.js +5 -2
- package/node_modules/@schibsted/tpaas-event-builder/dist/ejs/guards.js +4 -0
- package/node_modules/@schibsted/tpaas-event-builder/dist/ejs/index.js +1 -0
- package/node_modules/@schibsted/tpaas-event-builder/dist/index.d.ts +782 -164
- package/node_modules/@schibsted/tpaas-event-builder/package.json +1 -1
- package/node_modules/@schibsted/tpaas-schemas/dist/cjs/constants.js +66 -57
- package/node_modules/@schibsted/tpaas-schemas/dist/ejs/constants.js +64 -55
- package/node_modules/@schibsted/tpaas-schemas/dist/index.d.ts +836 -219
- package/package.json +5 -5
package/dist/cjs/Tracker.js
CHANGED
|
@@ -68,6 +68,7 @@ const version_1 = require("./version");
|
|
|
68
68
|
const visibility_observer_1 = require("./visibility-observer");
|
|
69
69
|
const warnOnce_1 = __importDefault(require("./warnOnce"));
|
|
70
70
|
const event_builder_1 = require("./wrapper/engagement/ui-element/event-builder");
|
|
71
|
+
const types_1 = require("./wrapper/types");
|
|
71
72
|
const page_from_page_view_1 = require("./wrapper/utils/page-from-page-view");
|
|
72
73
|
const event_builder_2 = require("./wrapper/view/article/event-builder");
|
|
73
74
|
const event_builder_3 = require("./wrapper/view/frontpage/event-builder");
|
|
@@ -157,6 +158,27 @@ function unsubscribe(eventName, callback) {
|
|
|
157
158
|
* @class Tracker
|
|
158
159
|
*/
|
|
159
160
|
class Tracker {
|
|
161
|
+
async getCurrentTpaasPage() {
|
|
162
|
+
const { id, type, url } = await this.currentPage;
|
|
163
|
+
return {
|
|
164
|
+
// remove the SDRN prefix, it's not expected for TPaaS
|
|
165
|
+
pageId: id.startsWith('sdrn:') ? id.split(':').slice(-1)[0] : id,
|
|
166
|
+
// make sure the pageType is valid for TPaaS
|
|
167
|
+
pageType: (0, tpaas_event_builder_1.isPageType)(type) ? type : 'Page',
|
|
168
|
+
url,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async getCurrentLegacyPage() {
|
|
172
|
+
const { id, type, url } = await this.currentPage;
|
|
173
|
+
// make sure the pageType is valid for legacy events
|
|
174
|
+
const validatedType = (0, types_1.isLegacyPageType)(type) ? type : 'Page';
|
|
175
|
+
return {
|
|
176
|
+
// add the SDRN prefix if it's not there, legacy events expect the full SDRN format
|
|
177
|
+
id: id.startsWith('sdrn:') ? id : `sdrn:${this.state.providerId}:${validatedType.toLowerCase()}:${id}`,
|
|
178
|
+
type: validatedType,
|
|
179
|
+
url,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
160
182
|
/**
|
|
161
183
|
* > [!WARNING]
|
|
162
184
|
* >
|
|
@@ -175,12 +197,18 @@ class Tracker {
|
|
|
175
197
|
// the tracking of the same element with both old and new events at the same time
|
|
176
198
|
this.legacyVisibilityObservers = new Map();
|
|
177
199
|
this.visibilityObservers = new Map();
|
|
178
|
-
|
|
179
|
-
|
|
200
|
+
/**
|
|
201
|
+
* @private
|
|
202
|
+
*
|
|
203
|
+
* This is used as the source of truth for the current page's information, which is updated based on the View Page-level event
|
|
204
|
+
* As a single source of truth for the current page, this needs to be compatible between legacy and TPaaS events, hence the need for a custom data type.
|
|
205
|
+
* Legacy events can provide an id with SDRN, while TPaaS events don't need it
|
|
206
|
+
*/
|
|
207
|
+
this.currentPage = {
|
|
208
|
+
id: 'unknown',
|
|
180
209
|
type: 'Page',
|
|
181
|
-
url:
|
|
210
|
+
url: pulse_utils_1.isBrowser ? window.location.href : undefined,
|
|
182
211
|
};
|
|
183
|
-
this.currentPage = undefined;
|
|
184
212
|
/**
|
|
185
213
|
* Promise that resolves when a session ID becomes available
|
|
186
214
|
* @private
|
|
@@ -192,7 +220,7 @@ class Tracker {
|
|
|
192
220
|
*/
|
|
193
221
|
this.hasProxyListener = false;
|
|
194
222
|
this.setPageDefaults = (pageViewEvent) => {
|
|
195
|
-
this.
|
|
223
|
+
this.currentPage = (0, page_from_page_view_1.pageFromPageView)(pageViewEvent);
|
|
196
224
|
if (pageViewEvent.origin) {
|
|
197
225
|
const pageDefaults = { origin: pageViewEvent.origin };
|
|
198
226
|
this.update(pageDefaults, true);
|
|
@@ -200,8 +228,8 @@ class Tracker {
|
|
|
200
228
|
};
|
|
201
229
|
this.setCurrentPage = (id, type) => {
|
|
202
230
|
this.currentPage = {
|
|
203
|
-
|
|
204
|
-
|
|
231
|
+
id: id || 'unknown',
|
|
232
|
+
type: type || 'Page',
|
|
205
233
|
url: pulse_utils_1.isBrowser ? window.location.href : undefined,
|
|
206
234
|
};
|
|
207
235
|
};
|
|
@@ -530,10 +558,11 @@ class Tracker {
|
|
|
530
558
|
source: source || 'unknown',
|
|
531
559
|
};
|
|
532
560
|
}
|
|
533
|
-
const [actor, consents, experiments] = await Promise.all([
|
|
561
|
+
const [actor, consents, experiments, currentPage] = await Promise.all([
|
|
534
562
|
this.getActor(),
|
|
535
563
|
this.getConsents(),
|
|
536
564
|
this.adaptExperiments(this.builders.experiments),
|
|
565
|
+
this.getCurrentTpaasPage(),
|
|
537
566
|
]);
|
|
538
567
|
const { environmentId, jwe } = (0, pulse_cis_sync_1.parsePulseCookies)();
|
|
539
568
|
return {
|
|
@@ -544,7 +573,7 @@ class Tracker {
|
|
|
544
573
|
consents: adaptConsentsToNewFormat(consents),
|
|
545
574
|
providerId: this.state.providerId,
|
|
546
575
|
pageviewId: this.state.pageViewId,
|
|
547
|
-
currentPage
|
|
576
|
+
currentPage,
|
|
548
577
|
experiments,
|
|
549
578
|
environmentId,
|
|
550
579
|
jwe,
|
|
@@ -621,7 +650,7 @@ class Tracker {
|
|
|
621
650
|
const viewUIElementInputProperties = buildEventFromElement(element);
|
|
622
651
|
this.trackLegacyViewUIElement(viewUIElementInputProperties);
|
|
623
652
|
}));
|
|
624
|
-
this.legacyVisibilityObservers.get(selector)?.observe(selector);
|
|
653
|
+
this.legacyVisibilityObservers.get(selector)?.observe(selector, {});
|
|
625
654
|
}
|
|
626
655
|
/**
|
|
627
656
|
* The `addUIElementVisibilityTracking` function provides an interface for sending tracking events when specific UI elements become visible within the viewport.
|
|
@@ -653,7 +682,38 @@ class Tracker {
|
|
|
653
682
|
const eventInput = buildEventFromElement(element);
|
|
654
683
|
this.trackImpressionUIElement(eventInput);
|
|
655
684
|
}));
|
|
656
|
-
this.visibilityObservers.get(selector)?.observe(selector);
|
|
685
|
+
this.visibilityObservers.get(selector)?.observe(selector, {});
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* The `addElementVisibilityObserver` function provides an interface for executing arbitrary code when specific DOM Elements become visible within the viewport.
|
|
689
|
+
* It is built on the `VisibilityObserver` class, which efficiently detects visibility changes in DOM elements.
|
|
690
|
+
* When an element becomes at least 2/3 visible, a callback is triggered, with the Element that invoked the callback call as argument.
|
|
691
|
+
* It leverages the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) for viewport tracking
|
|
692
|
+
* and the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to monitor lazy-loaded elements.
|
|
693
|
+
*
|
|
694
|
+
* Example usage:
|
|
695
|
+
*
|
|
696
|
+
* ```ts
|
|
697
|
+
* tracker.addElementVisibilityObserver('[data-pulse-entity-id]', (element) => {
|
|
698
|
+
* const eventInput = parseElementIntoEventInput(element); // user-defined function
|
|
699
|
+
* tracker.trackImpressionTeaser(eventInput); // can be anything actually, not necessarily a Teaser tracking call
|
|
700
|
+
* });
|
|
701
|
+
* ```
|
|
702
|
+
* @param selector
|
|
703
|
+
* @param callback
|
|
704
|
+
* @param options {ObserverOptions}
|
|
705
|
+
* @param options.threshold - A number between 0 and 1 indicating how much of the element should be visible to trigger
|
|
706
|
+
* the callback. Default is 0.67 (2/3 of the element).
|
|
707
|
+
*/
|
|
708
|
+
addElementVisibilityObserver(selector, callback, options = {}) {
|
|
709
|
+
if (this.visibilityObservers.has(selector)) {
|
|
710
|
+
console.warn(`Cannot add UI Element visibility tracking for selector "${selector}" as it is already being tracked.`);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
this.visibilityObservers.set(selector, new visibility_observer_1.VisibilityObserver((element) => {
|
|
714
|
+
callback(element);
|
|
715
|
+
}));
|
|
716
|
+
this.visibilityObservers.get(selector)?.observe(selector, options);
|
|
657
717
|
}
|
|
658
718
|
/**
|
|
659
719
|
* The old trackViewArticle method from the Media Wrapper.
|
|
@@ -747,7 +807,7 @@ class Tracker {
|
|
|
747
807
|
* @category Legacy Tracking
|
|
748
808
|
*/
|
|
749
809
|
trackLegacyViewUIElement(inputProperties, trackOptions) {
|
|
750
|
-
const viewUIElementEventData = (0, event_builder_8.buildLegacyViewUIElementEvent)(inputProperties, this.
|
|
810
|
+
const viewUIElementEventData = (0, event_builder_8.buildLegacyViewUIElementEvent)(inputProperties, this.getCurrentLegacyPage());
|
|
751
811
|
return this.track('trackerEvent', viewUIElementEventData, trackOptions);
|
|
752
812
|
}
|
|
753
813
|
/**
|
|
@@ -758,7 +818,7 @@ class Tracker {
|
|
|
758
818
|
* @category Legacy Tracking
|
|
759
819
|
*/
|
|
760
820
|
trackLegacyClickUIElement(inputProperties, trackOptions) {
|
|
761
|
-
const clickUIElementEventData = (0, event_builder_1.buildClickUIElementEvent)(inputProperties, this.
|
|
821
|
+
const clickUIElementEventData = (0, event_builder_1.buildClickUIElementEvent)(inputProperties, this.getCurrentLegacyPage());
|
|
762
822
|
return this.track('trackerEvent', clickUIElementEventData, trackOptions);
|
|
763
823
|
}
|
|
764
824
|
/**
|
|
@@ -831,10 +891,9 @@ class Tracker {
|
|
|
831
891
|
const result = this.sendTpaasEvent(event);
|
|
832
892
|
if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
|
|
833
893
|
const leavePageCallback = async (leave) => {
|
|
834
|
-
const { metrics, ...rest } = input; // metrics are not part of LeavePage schema, so we need to omit them
|
|
835
894
|
await this.trackLeavePage({
|
|
836
895
|
leave,
|
|
837
|
-
...
|
|
896
|
+
...input,
|
|
838
897
|
});
|
|
839
898
|
};
|
|
840
899
|
(0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
|
|
@@ -905,12 +964,9 @@ class Tracker {
|
|
|
905
964
|
const result = this.sendTpaasEvent(event);
|
|
906
965
|
if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
|
|
907
966
|
const leaveArticleCallback = async (leave) => {
|
|
908
|
-
// omit these properties from LeaveArticle because they're not part of the LeaveArticle schema
|
|
909
|
-
const { authors, hotness, lifetime, sponsor, wordCount, ...rest } = input.object;
|
|
910
967
|
await this.trackLeaveArticle({
|
|
911
968
|
leave,
|
|
912
969
|
...input,
|
|
913
|
-
object: rest,
|
|
914
970
|
});
|
|
915
971
|
};
|
|
916
972
|
(0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
|
|
@@ -1020,6 +1076,15 @@ class Tracker {
|
|
|
1020
1076
|
}
|
|
1021
1077
|
return result;
|
|
1022
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* @param input
|
|
1081
|
+
* @category TPaaS Tracking
|
|
1082
|
+
*/
|
|
1083
|
+
async trackImpressionAdSlot(input) {
|
|
1084
|
+
const dependencies = await this.prepareTpaasEvent();
|
|
1085
|
+
const event = (0, tpaas_event_builder_1.buildImpressionAdSlotEvent)(input, dependencies);
|
|
1086
|
+
return this.sendTpaasEvent(event);
|
|
1087
|
+
}
|
|
1023
1088
|
/**
|
|
1024
1089
|
* @param input
|
|
1025
1090
|
* @category TPaaS Tracking
|
|
@@ -1078,10 +1143,9 @@ class Tracker {
|
|
|
1078
1143
|
const result = this.sendTpaasEvent(event);
|
|
1079
1144
|
if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
|
|
1080
1145
|
const leaveFrontpageCallback = async (leave) => {
|
|
1081
|
-
const { metrics, ...rest } = input; // metrics are not part of LeaveFrontpage schema, so we need to omit them
|
|
1082
1146
|
await this.trackLeaveFrontpage({
|
|
1083
1147
|
leave,
|
|
1084
|
-
...
|
|
1148
|
+
...input,
|
|
1085
1149
|
});
|
|
1086
1150
|
};
|
|
1087
1151
|
(0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
|
|
@@ -1134,6 +1198,10 @@ class Tracker {
|
|
|
1134
1198
|
await this.trackLeaveListing({
|
|
1135
1199
|
leave,
|
|
1136
1200
|
...input,
|
|
1201
|
+
object: {
|
|
1202
|
+
...input.object,
|
|
1203
|
+
listingType: 'Tag,Story,Section,Search,Author,Other', // TODO: listingType is broken for this catalog. will be fixed in the next one
|
|
1204
|
+
},
|
|
1137
1205
|
});
|
|
1138
1206
|
};
|
|
1139
1207
|
(0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
|
|
@@ -1157,12 +1225,9 @@ class Tracker {
|
|
|
1157
1225
|
const result = this.sendTpaasEvent(event);
|
|
1158
1226
|
if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
|
|
1159
1227
|
const leaveLockedArticleCallback = async (leave) => {
|
|
1160
|
-
// omit these properties because they're not part of the LeaveLockedArticle schema
|
|
1161
|
-
const { authors, hotness, lifetime, sponsor, wordCount, ...rest } = input.object;
|
|
1162
1228
|
await this.trackLeaveLockedArticle({
|
|
1163
1229
|
leave,
|
|
1164
1230
|
...input,
|
|
1165
|
-
object: rest,
|
|
1166
1231
|
});
|
|
1167
1232
|
};
|
|
1168
1233
|
(0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
|
|
@@ -1308,11 +1373,9 @@ class Tracker {
|
|
|
1308
1373
|
const result = this.sendTpaasEvent(event);
|
|
1309
1374
|
if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
|
|
1310
1375
|
const leaveWeatherCallback = async (leave) => {
|
|
1311
|
-
// omit object and page from input because they're not part of the LeaveWeather schema
|
|
1312
|
-
const { object, page, ...rest } = input;
|
|
1313
1376
|
await this.trackLeaveWeather({
|
|
1314
1377
|
leave,
|
|
1315
|
-
...
|
|
1378
|
+
...input,
|
|
1316
1379
|
});
|
|
1317
1380
|
};
|
|
1318
1381
|
(0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
|
package/dist/cjs/version.js
CHANGED
|
@@ -16,14 +16,16 @@ class VisibilityObserver {
|
|
|
16
16
|
* Uses {@link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver | MutationObserver} to monitor the DOM for dynamically loaded content.
|
|
17
17
|
* Callback is invoked only once per element.
|
|
18
18
|
* @param selector The CSS selector for elements to track.
|
|
19
|
+
* @param options {ObserverOptions} Optional configuration for the observer.
|
|
20
|
+
* @param options.threshold Which ratio triggers the Observer callback.
|
|
19
21
|
* @throws Error if called more than once.
|
|
20
22
|
*/
|
|
21
|
-
observe(selector) {
|
|
23
|
+
observe(selector, { threshold = 0.67 }) {
|
|
22
24
|
if (this.selector) {
|
|
23
25
|
throw new Error(`Cannot observe selector ${selector}. A visibility observer has already been set up for selector ${this.selector}.`);
|
|
24
26
|
}
|
|
25
27
|
this.selector = selector;
|
|
26
|
-
this.setupObservers();
|
|
28
|
+
this.setupObservers({ threshold });
|
|
27
29
|
// biome-ignore lint/suspicious/useIterableCallbackReturn: -
|
|
28
30
|
this.elements().forEach((elementToTrack) => this.observeIntersection(elementToTrack));
|
|
29
31
|
}
|
|
@@ -33,14 +35,14 @@ class VisibilityObserver {
|
|
|
33
35
|
elements() {
|
|
34
36
|
return Array.from(document.querySelectorAll(this.selector));
|
|
35
37
|
}
|
|
36
|
-
//
|
|
37
|
-
setupObservers() {
|
|
38
|
-
this.setupIntersectionObserver();
|
|
38
|
+
// Initialise both observers and start watching for DOM changes
|
|
39
|
+
setupObservers({ threshold }) {
|
|
40
|
+
this.setupIntersectionObserver({ threshold });
|
|
39
41
|
this.setupMutationObserver();
|
|
40
42
|
this.observeMutations();
|
|
41
43
|
}
|
|
42
44
|
// Create observer to detect when elements become visible in the viewport
|
|
43
|
-
setupIntersectionObserver() {
|
|
45
|
+
setupIntersectionObserver({ threshold }) {
|
|
44
46
|
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
45
47
|
entries.forEach((entry) => {
|
|
46
48
|
if (entry.isIntersecting) {
|
|
@@ -49,7 +51,7 @@ class VisibilityObserver {
|
|
|
49
51
|
this.intersectionObserver.unobserve(node);
|
|
50
52
|
}
|
|
51
53
|
});
|
|
52
|
-
}, { threshold
|
|
54
|
+
}, { threshold });
|
|
53
55
|
}
|
|
54
56
|
// Create observer to detect DOM changes that may add/remove tracked elements
|
|
55
57
|
setupMutationObserver() {
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLegacyPageType = isLegacyPageType;
|
|
4
|
+
const LEGACY_PAGE_TYPE_VALUES = ['Article', 'Listing', 'Frontpage', 'SalesPoster', 'Page', 'LoginPoster'];
|
|
5
|
+
function isLegacyPageType(value) {
|
|
6
|
+
return LEGACY_PAGE_TYPE_VALUES.includes(value);
|
|
7
|
+
}
|
package/dist/ejs/Tracker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parsePulseCookies } from '@schibsted/pulse-cis-sync';
|
|
2
2
|
import { evaluateAndFlatten, isBrowser, isFunction, isPromise, isString, pulseMerge, throttle, } from '@schibsted/pulse-utils';
|
|
3
|
-
import { buildAnonymousViewUIElementEvent, buildCompletedHealthActionEvent, buildEngagementAudioEvent, buildEngagementFormEvent, buildEngagementHealthUIElementEvent, buildEngagementOfferEvent, buildEngagementTeaserEvent, buildEngagementUIElementEvent, buildEngagementVideoAdEvent, buildEngagementVideoEvent, buildEngagementWidgetEvent, buildImpressionFormEvent, buildImpressionHealthUIElementEvent, buildImpressionOfferEvent, buildImpressionPlayerEvent, buildImpressionTeaserEvent, buildImpressionUIElementEvent, buildImpressionWidgetEvent, buildLeaveArticleEvent, buildLeaveAudioPageEvent, buildLeaveErrorEvent, buildLeaveFrontpageEvent, buildLeaveLandingpageEvent, buildLeaveListingEvent, buildLeaveLockedArticleEvent, buildLeaveLockedAudioPageEvent, buildLeaveLockedVideoPageEvent, buildLeavePageEvent, buildLeaveSportsPageEvent, buildLeaveVideoPageEvent, buildLeaveWeatherEvent, buildViewArticleEvent, buildViewAudioPageEvent, buildViewErrorEvent, buildViewFrontpageEvent, buildViewHealthPageEvent, buildViewLandingpageEvent, buildViewListingEvent, buildViewLockedArticleEvent, buildViewLockedAudioPageEvent, buildViewLockedVideoPageEvent, buildViewPageEvent, buildViewSportsPageEvent, buildViewTitleEvent, buildViewVideoPageEvent, buildViewWeatherEvent, getCommonBuildersOutput, } from '@schibsted/tpaas-event-builder';
|
|
3
|
+
import { buildAnonymousViewUIElementEvent, buildCompletedHealthActionEvent, buildEngagementAudioEvent, buildEngagementFormEvent, buildEngagementHealthUIElementEvent, buildEngagementOfferEvent, buildEngagementTeaserEvent, buildEngagementUIElementEvent, buildEngagementVideoAdEvent, buildEngagementVideoEvent, buildEngagementWidgetEvent, buildImpressionAdSlotEvent, buildImpressionFormEvent, buildImpressionHealthUIElementEvent, buildImpressionOfferEvent, buildImpressionPlayerEvent, buildImpressionTeaserEvent, buildImpressionUIElementEvent, buildImpressionWidgetEvent, buildLeaveArticleEvent, buildLeaveAudioPageEvent, buildLeaveErrorEvent, buildLeaveFrontpageEvent, buildLeaveLandingpageEvent, buildLeaveListingEvent, buildLeaveLockedArticleEvent, buildLeaveLockedAudioPageEvent, buildLeaveLockedVideoPageEvent, buildLeavePageEvent, buildLeaveSportsPageEvent, buildLeaveVideoPageEvent, buildLeaveWeatherEvent, buildViewArticleEvent, buildViewAudioPageEvent, buildViewErrorEvent, buildViewFrontpageEvent, buildViewHealthPageEvent, buildViewLandingpageEvent, buildViewListingEvent, buildViewLockedArticleEvent, buildViewLockedAudioPageEvent, buildViewLockedVideoPageEvent, buildViewPageEvent, buildViewSportsPageEvent, buildViewTitleEvent, buildViewVideoPageEvent, buildViewWeatherEvent, getCommonBuildersOutput, isPageType, } from '@schibsted/tpaas-event-builder';
|
|
4
4
|
import { LeaveArticle, LeaveAudioPage, LeaveError, LeaveFrontpage, LeaveLandingpage, LeaveListing, LeaveLockedArticle, LeaveLockedAudioPage, LeaveLockedVideoPage, LeavePage, LeaveSportsPage, LeaveVideoPage, LeaveWeather, ViewArticle, ViewAudioPage, ViewError, ViewFrontpage, ViewHealthPage, ViewLandingpage, ViewListing, ViewLockedArticle, ViewLockedAudioPage, ViewLockedVideoPage, ViewPage, ViewSportsPage, ViewTitle, ViewVideoPage, ViewWeather, } from '@schibsted/tpaas-schemas';
|
|
5
5
|
import logger from 'loglevel';
|
|
6
6
|
import { v4 } from 'uuid';
|
|
@@ -25,6 +25,7 @@ import { SDK_VERSION } from './version';
|
|
|
25
25
|
import { VisibilityObserver } from './visibility-observer';
|
|
26
26
|
import warnOnce from './warnOnce';
|
|
27
27
|
import { buildClickUIElementEvent } from './wrapper/engagement/ui-element/event-builder';
|
|
28
|
+
import { isLegacyPageType } from './wrapper/types';
|
|
28
29
|
import { pageFromPageView } from './wrapper/utils/page-from-page-view';
|
|
29
30
|
import { buildLegacyViewArticleEvent } from './wrapper/view/article/event-builder';
|
|
30
31
|
import { buildLegacyViewFrontpageEvent } from './wrapper/view/frontpage/event-builder';
|
|
@@ -114,6 +115,27 @@ export function unsubscribe(eventName, callback) {
|
|
|
114
115
|
* @class Tracker
|
|
115
116
|
*/
|
|
116
117
|
export default class Tracker {
|
|
118
|
+
async getCurrentTpaasPage() {
|
|
119
|
+
const { id, type, url } = await this.currentPage;
|
|
120
|
+
return {
|
|
121
|
+
// remove the SDRN prefix, it's not expected for TPaaS
|
|
122
|
+
pageId: id.startsWith('sdrn:') ? id.split(':').slice(-1)[0] : id,
|
|
123
|
+
// make sure the pageType is valid for TPaaS
|
|
124
|
+
pageType: isPageType(type) ? type : 'Page',
|
|
125
|
+
url,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async getCurrentLegacyPage() {
|
|
129
|
+
const { id, type, url } = await this.currentPage;
|
|
130
|
+
// make sure the pageType is valid for legacy events
|
|
131
|
+
const validatedType = isLegacyPageType(type) ? type : 'Page';
|
|
132
|
+
return {
|
|
133
|
+
// add the SDRN prefix if it's not there, legacy events expect the full SDRN format
|
|
134
|
+
id: id.startsWith('sdrn:') ? id : `sdrn:${this.state.providerId}:${validatedType.toLowerCase()}:${id}`,
|
|
135
|
+
type: validatedType,
|
|
136
|
+
url,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
117
139
|
/**
|
|
118
140
|
* > [!WARNING]
|
|
119
141
|
* >
|
|
@@ -132,12 +154,18 @@ export default class Tracker {
|
|
|
132
154
|
// the tracking of the same element with both old and new events at the same time
|
|
133
155
|
this.legacyVisibilityObservers = new Map();
|
|
134
156
|
this.visibilityObservers = new Map();
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
/**
|
|
158
|
+
* @private
|
|
159
|
+
*
|
|
160
|
+
* This is used as the source of truth for the current page's information, which is updated based on the View Page-level event
|
|
161
|
+
* As a single source of truth for the current page, this needs to be compatible between legacy and TPaaS events, hence the need for a custom data type.
|
|
162
|
+
* Legacy events can provide an id with SDRN, while TPaaS events don't need it
|
|
163
|
+
*/
|
|
164
|
+
this.currentPage = {
|
|
165
|
+
id: 'unknown',
|
|
137
166
|
type: 'Page',
|
|
138
|
-
url:
|
|
167
|
+
url: isBrowser ? window.location.href : undefined,
|
|
139
168
|
};
|
|
140
|
-
this.currentPage = undefined;
|
|
141
169
|
/**
|
|
142
170
|
* Promise that resolves when a session ID becomes available
|
|
143
171
|
* @private
|
|
@@ -149,7 +177,7 @@ export default class Tracker {
|
|
|
149
177
|
*/
|
|
150
178
|
this.hasProxyListener = false;
|
|
151
179
|
this.setPageDefaults = (pageViewEvent) => {
|
|
152
|
-
this.
|
|
180
|
+
this.currentPage = pageFromPageView(pageViewEvent);
|
|
153
181
|
if (pageViewEvent.origin) {
|
|
154
182
|
const pageDefaults = { origin: pageViewEvent.origin };
|
|
155
183
|
this.update(pageDefaults, true);
|
|
@@ -157,8 +185,8 @@ export default class Tracker {
|
|
|
157
185
|
};
|
|
158
186
|
this.setCurrentPage = (id, type) => {
|
|
159
187
|
this.currentPage = {
|
|
160
|
-
|
|
161
|
-
|
|
188
|
+
id: id || 'unknown',
|
|
189
|
+
type: type || 'Page',
|
|
162
190
|
url: isBrowser ? window.location.href : undefined,
|
|
163
191
|
};
|
|
164
192
|
};
|
|
@@ -487,10 +515,11 @@ export default class Tracker {
|
|
|
487
515
|
source: source || 'unknown',
|
|
488
516
|
};
|
|
489
517
|
}
|
|
490
|
-
const [actor, consents, experiments] = await Promise.all([
|
|
518
|
+
const [actor, consents, experiments, currentPage] = await Promise.all([
|
|
491
519
|
this.getActor(),
|
|
492
520
|
this.getConsents(),
|
|
493
521
|
this.adaptExperiments(this.builders.experiments),
|
|
522
|
+
this.getCurrentTpaasPage(),
|
|
494
523
|
]);
|
|
495
524
|
const { environmentId, jwe } = parsePulseCookies();
|
|
496
525
|
return {
|
|
@@ -501,7 +530,7 @@ export default class Tracker {
|
|
|
501
530
|
consents: adaptConsentsToNewFormat(consents),
|
|
502
531
|
providerId: this.state.providerId,
|
|
503
532
|
pageviewId: this.state.pageViewId,
|
|
504
|
-
currentPage
|
|
533
|
+
currentPage,
|
|
505
534
|
experiments,
|
|
506
535
|
environmentId,
|
|
507
536
|
jwe,
|
|
@@ -578,7 +607,7 @@ export default class Tracker {
|
|
|
578
607
|
const viewUIElementInputProperties = buildEventFromElement(element);
|
|
579
608
|
this.trackLegacyViewUIElement(viewUIElementInputProperties);
|
|
580
609
|
}));
|
|
581
|
-
this.legacyVisibilityObservers.get(selector)?.observe(selector);
|
|
610
|
+
this.legacyVisibilityObservers.get(selector)?.observe(selector, {});
|
|
582
611
|
}
|
|
583
612
|
/**
|
|
584
613
|
* The `addUIElementVisibilityTracking` function provides an interface for sending tracking events when specific UI elements become visible within the viewport.
|
|
@@ -610,7 +639,38 @@ export default class Tracker {
|
|
|
610
639
|
const eventInput = buildEventFromElement(element);
|
|
611
640
|
this.trackImpressionUIElement(eventInput);
|
|
612
641
|
}));
|
|
613
|
-
this.visibilityObservers.get(selector)?.observe(selector);
|
|
642
|
+
this.visibilityObservers.get(selector)?.observe(selector, {});
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* The `addElementVisibilityObserver` function provides an interface for executing arbitrary code when specific DOM Elements become visible within the viewport.
|
|
646
|
+
* It is built on the `VisibilityObserver` class, which efficiently detects visibility changes in DOM elements.
|
|
647
|
+
* When an element becomes at least 2/3 visible, a callback is triggered, with the Element that invoked the callback call as argument.
|
|
648
|
+
* It leverages the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) for viewport tracking
|
|
649
|
+
* and the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to monitor lazy-loaded elements.
|
|
650
|
+
*
|
|
651
|
+
* Example usage:
|
|
652
|
+
*
|
|
653
|
+
* ```ts
|
|
654
|
+
* tracker.addElementVisibilityObserver('[data-pulse-entity-id]', (element) => {
|
|
655
|
+
* const eventInput = parseElementIntoEventInput(element); // user-defined function
|
|
656
|
+
* tracker.trackImpressionTeaser(eventInput); // can be anything actually, not necessarily a Teaser tracking call
|
|
657
|
+
* });
|
|
658
|
+
* ```
|
|
659
|
+
* @param selector
|
|
660
|
+
* @param callback
|
|
661
|
+
* @param options {ObserverOptions}
|
|
662
|
+
* @param options.threshold - A number between 0 and 1 indicating how much of the element should be visible to trigger
|
|
663
|
+
* the callback. Default is 0.67 (2/3 of the element).
|
|
664
|
+
*/
|
|
665
|
+
addElementVisibilityObserver(selector, callback, options = {}) {
|
|
666
|
+
if (this.visibilityObservers.has(selector)) {
|
|
667
|
+
console.warn(`Cannot add UI Element visibility tracking for selector "${selector}" as it is already being tracked.`);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
this.visibilityObservers.set(selector, new VisibilityObserver((element) => {
|
|
671
|
+
callback(element);
|
|
672
|
+
}));
|
|
673
|
+
this.visibilityObservers.get(selector)?.observe(selector, options);
|
|
614
674
|
}
|
|
615
675
|
/**
|
|
616
676
|
* The old trackViewArticle method from the Media Wrapper.
|
|
@@ -704,7 +764,7 @@ export default class Tracker {
|
|
|
704
764
|
* @category Legacy Tracking
|
|
705
765
|
*/
|
|
706
766
|
trackLegacyViewUIElement(inputProperties, trackOptions) {
|
|
707
|
-
const viewUIElementEventData = buildLegacyViewUIElementEvent(inputProperties, this.
|
|
767
|
+
const viewUIElementEventData = buildLegacyViewUIElementEvent(inputProperties, this.getCurrentLegacyPage());
|
|
708
768
|
return this.track('trackerEvent', viewUIElementEventData, trackOptions);
|
|
709
769
|
}
|
|
710
770
|
/**
|
|
@@ -715,7 +775,7 @@ export default class Tracker {
|
|
|
715
775
|
* @category Legacy Tracking
|
|
716
776
|
*/
|
|
717
777
|
trackLegacyClickUIElement(inputProperties, trackOptions) {
|
|
718
|
-
const clickUIElementEventData = buildClickUIElementEvent(inputProperties, this.
|
|
778
|
+
const clickUIElementEventData = buildClickUIElementEvent(inputProperties, this.getCurrentLegacyPage());
|
|
719
779
|
return this.track('trackerEvent', clickUIElementEventData, trackOptions);
|
|
720
780
|
}
|
|
721
781
|
/**
|
|
@@ -788,10 +848,9 @@ export default class Tracker {
|
|
|
788
848
|
const result = this.sendTpaasEvent(event);
|
|
789
849
|
if (shouldEnableLeaveTracking(options.leaveTracking)) {
|
|
790
850
|
const leavePageCallback = async (leave) => {
|
|
791
|
-
const { metrics, ...rest } = input; // metrics are not part of LeavePage schema, so we need to omit them
|
|
792
851
|
await this.trackLeavePage({
|
|
793
852
|
leave,
|
|
794
|
-
...
|
|
853
|
+
...input,
|
|
795
854
|
});
|
|
796
855
|
};
|
|
797
856
|
addTpaasLeaveTracking({
|
|
@@ -862,12 +921,9 @@ export default class Tracker {
|
|
|
862
921
|
const result = this.sendTpaasEvent(event);
|
|
863
922
|
if (shouldEnableLeaveTracking(options.leaveTracking)) {
|
|
864
923
|
const leaveArticleCallback = async (leave) => {
|
|
865
|
-
// omit these properties from LeaveArticle because they're not part of the LeaveArticle schema
|
|
866
|
-
const { authors, hotness, lifetime, sponsor, wordCount, ...rest } = input.object;
|
|
867
924
|
await this.trackLeaveArticle({
|
|
868
925
|
leave,
|
|
869
926
|
...input,
|
|
870
|
-
object: rest,
|
|
871
927
|
});
|
|
872
928
|
};
|
|
873
929
|
addTpaasLeaveTracking({
|
|
@@ -977,6 +1033,15 @@ export default class Tracker {
|
|
|
977
1033
|
}
|
|
978
1034
|
return result;
|
|
979
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* @param input
|
|
1038
|
+
* @category TPaaS Tracking
|
|
1039
|
+
*/
|
|
1040
|
+
async trackImpressionAdSlot(input) {
|
|
1041
|
+
const dependencies = await this.prepareTpaasEvent();
|
|
1042
|
+
const event = buildImpressionAdSlotEvent(input, dependencies);
|
|
1043
|
+
return this.sendTpaasEvent(event);
|
|
1044
|
+
}
|
|
980
1045
|
/**
|
|
981
1046
|
* @param input
|
|
982
1047
|
* @category TPaaS Tracking
|
|
@@ -1035,10 +1100,9 @@ export default class Tracker {
|
|
|
1035
1100
|
const result = this.sendTpaasEvent(event);
|
|
1036
1101
|
if (shouldEnableLeaveTracking(options.leaveTracking)) {
|
|
1037
1102
|
const leaveFrontpageCallback = async (leave) => {
|
|
1038
|
-
const { metrics, ...rest } = input; // metrics are not part of LeaveFrontpage schema, so we need to omit them
|
|
1039
1103
|
await this.trackLeaveFrontpage({
|
|
1040
1104
|
leave,
|
|
1041
|
-
...
|
|
1105
|
+
...input,
|
|
1042
1106
|
});
|
|
1043
1107
|
};
|
|
1044
1108
|
addTpaasLeaveTracking({
|
|
@@ -1091,6 +1155,10 @@ export default class Tracker {
|
|
|
1091
1155
|
await this.trackLeaveListing({
|
|
1092
1156
|
leave,
|
|
1093
1157
|
...input,
|
|
1158
|
+
object: {
|
|
1159
|
+
...input.object,
|
|
1160
|
+
listingType: 'Tag,Story,Section,Search,Author,Other', // TODO: listingType is broken for this catalog. will be fixed in the next one
|
|
1161
|
+
},
|
|
1094
1162
|
});
|
|
1095
1163
|
};
|
|
1096
1164
|
addTpaasLeaveTracking({
|
|
@@ -1114,12 +1182,9 @@ export default class Tracker {
|
|
|
1114
1182
|
const result = this.sendTpaasEvent(event);
|
|
1115
1183
|
if (shouldEnableLeaveTracking(options.leaveTracking)) {
|
|
1116
1184
|
const leaveLockedArticleCallback = async (leave) => {
|
|
1117
|
-
// omit these properties because they're not part of the LeaveLockedArticle schema
|
|
1118
|
-
const { authors, hotness, lifetime, sponsor, wordCount, ...rest } = input.object;
|
|
1119
1185
|
await this.trackLeaveLockedArticle({
|
|
1120
1186
|
leave,
|
|
1121
1187
|
...input,
|
|
1122
|
-
object: rest,
|
|
1123
1188
|
});
|
|
1124
1189
|
};
|
|
1125
1190
|
addTpaasLeaveTracking({
|
|
@@ -1265,11 +1330,9 @@ export default class Tracker {
|
|
|
1265
1330
|
const result = this.sendTpaasEvent(event);
|
|
1266
1331
|
if (shouldEnableLeaveTracking(options.leaveTracking)) {
|
|
1267
1332
|
const leaveWeatherCallback = async (leave) => {
|
|
1268
|
-
// omit object and page from input because they're not part of the LeaveWeather schema
|
|
1269
|
-
const { object, page, ...rest } = input;
|
|
1270
1333
|
await this.trackLeaveWeather({
|
|
1271
1334
|
leave,
|
|
1272
|
-
...
|
|
1335
|
+
...input,
|
|
1273
1336
|
});
|
|
1274
1337
|
};
|
|
1275
1338
|
addTpaasLeaveTracking({
|
package/dist/ejs/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is auto-generated by bin/generate-version.js. Do not edit manually.
|
|
2
|
-
export const SDK_VERSION = '8.0.0-rc.
|
|
2
|
+
export const SDK_VERSION = '8.0.0-rc.7';
|
|
@@ -13,14 +13,16 @@ export class VisibilityObserver {
|
|
|
13
13
|
* Uses {@link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver | MutationObserver} to monitor the DOM for dynamically loaded content.
|
|
14
14
|
* Callback is invoked only once per element.
|
|
15
15
|
* @param selector The CSS selector for elements to track.
|
|
16
|
+
* @param options {ObserverOptions} Optional configuration for the observer.
|
|
17
|
+
* @param options.threshold Which ratio triggers the Observer callback.
|
|
16
18
|
* @throws Error if called more than once.
|
|
17
19
|
*/
|
|
18
|
-
observe(selector) {
|
|
20
|
+
observe(selector, { threshold = 0.67 }) {
|
|
19
21
|
if (this.selector) {
|
|
20
22
|
throw new Error(`Cannot observe selector ${selector}. A visibility observer has already been set up for selector ${this.selector}.`);
|
|
21
23
|
}
|
|
22
24
|
this.selector = selector;
|
|
23
|
-
this.setupObservers();
|
|
25
|
+
this.setupObservers({ threshold });
|
|
24
26
|
// biome-ignore lint/suspicious/useIterableCallbackReturn: -
|
|
25
27
|
this.elements().forEach((elementToTrack) => this.observeIntersection(elementToTrack));
|
|
26
28
|
}
|
|
@@ -30,14 +32,14 @@ export class VisibilityObserver {
|
|
|
30
32
|
elements() {
|
|
31
33
|
return Array.from(document.querySelectorAll(this.selector));
|
|
32
34
|
}
|
|
33
|
-
//
|
|
34
|
-
setupObservers() {
|
|
35
|
-
this.setupIntersectionObserver();
|
|
35
|
+
// Initialise both observers and start watching for DOM changes
|
|
36
|
+
setupObservers({ threshold }) {
|
|
37
|
+
this.setupIntersectionObserver({ threshold });
|
|
36
38
|
this.setupMutationObserver();
|
|
37
39
|
this.observeMutations();
|
|
38
40
|
}
|
|
39
41
|
// Create observer to detect when elements become visible in the viewport
|
|
40
|
-
setupIntersectionObserver() {
|
|
42
|
+
setupIntersectionObserver({ threshold }) {
|
|
41
43
|
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
42
44
|
entries.forEach((entry) => {
|
|
43
45
|
if (entry.isIntersecting) {
|
|
@@ -46,7 +48,7 @@ export class VisibilityObserver {
|
|
|
46
48
|
this.intersectionObserver.unobserve(node);
|
|
47
49
|
}
|
|
48
50
|
});
|
|
49
|
-
}, { threshold
|
|
51
|
+
}, { threshold });
|
|
50
52
|
}
|
|
51
53
|
// Create observer to detect DOM changes that may add/remove tracked elements
|
|
52
54
|
setupMutationObserver() {
|