@schibsted/pulse-sdk 8.0.0-rc.6 → 8.0.0-rc.8

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 (35) hide show
  1. package/dist/cjs/Tracker.js +92 -43
  2. package/dist/cjs/builders/experiments.js +1 -0
  3. package/dist/cjs/tracker-proxy/consts.js +1 -0
  4. package/dist/cjs/version.js +1 -1
  5. package/dist/cjs/visibility-observer/index.js +9 -7
  6. package/dist/cjs/wrapper/types/event.js +5 -0
  7. package/dist/ejs/Tracker.js +93 -44
  8. package/dist/ejs/builders/experiments.js +1 -1
  9. package/dist/ejs/tracker-proxy/consts.js +1 -0
  10. package/dist/ejs/version.js +1 -1
  11. package/dist/ejs/visibility-observer/index.js +9 -7
  12. package/dist/ejs/wrapper/types/event.js +4 -1
  13. package/dist/types/Tracker.d.ts +39 -17
  14. package/dist/types/builders/experiments.d.ts +1 -0
  15. package/dist/types/index.d.ts +0 -1
  16. package/dist/types/tracker-proxy/consts.d.ts +1 -1
  17. package/dist/types/version.d.ts +1 -1
  18. package/dist/types/visibility-observer/index.d.ts +6 -1
  19. package/dist/types/wrapper/types/event.d.ts +3 -1
  20. package/node_modules/@schibsted/tpaas-event-builder/dist/cjs/builders.js +53 -47
  21. package/node_modules/@schibsted/tpaas-event-builder/dist/cjs/guards.js +7 -0
  22. package/node_modules/@schibsted/tpaas-event-builder/dist/cjs/index.js +3 -0
  23. package/node_modules/@schibsted/tpaas-event-builder/dist/ejs/builders.js +53 -48
  24. package/node_modules/@schibsted/tpaas-event-builder/dist/ejs/guards.js +4 -0
  25. package/node_modules/@schibsted/tpaas-event-builder/dist/ejs/index.js +1 -0
  26. package/node_modules/@schibsted/tpaas-event-builder/dist/index.d.ts +822 -198
  27. package/node_modules/@schibsted/tpaas-event-builder/package.json +2 -1
  28. package/node_modules/@schibsted/tpaas-schemas/dist/cjs/constants.js +66 -57
  29. package/node_modules/@schibsted/tpaas-schemas/dist/cjs/index.js +1 -0
  30. package/node_modules/@schibsted/tpaas-schemas/dist/cjs/types.js +2 -0
  31. package/node_modules/@schibsted/tpaas-schemas/dist/ejs/constants.js +64 -55
  32. package/node_modules/@schibsted/tpaas-schemas/dist/ejs/index.js +1 -0
  33. package/node_modules/@schibsted/tpaas-schemas/dist/ejs/types.js +1 -0
  34. package/node_modules/@schibsted/tpaas-schemas/dist/index.d.ts +888 -223
  35. package/package.json +4 -4
@@ -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, 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';
@@ -10,6 +10,7 @@ import { getConsentsWaitForAnalytics, getFallbackConsents } from './builders/con
10
10
  import { applyAdvertisingConsent, applyAdvertisingConsentSync, } from './builders/consents';
11
11
  import { engagementEvent, identityEvent, routableEvent, trackerEvent } from './builders/events';
12
12
  import { anonymousEngagementEvent, anonymousTrackerEvent } from './builders/events-anonymous-node';
13
+ import { generateExperimentSDRN } from './builders/experiments';
13
14
  import { readSession } from './builders/session-browser';
14
15
  import { baseConfig as defaultConfig } from './config';
15
16
  import { CISEndpoints, collectorEndpoints, remoteConfig } from './config-browser';
@@ -25,6 +26,7 @@ import { SDK_VERSION } from './version';
25
26
  import { VisibilityObserver } from './visibility-observer';
26
27
  import warnOnce from './warnOnce';
27
28
  import { buildClickUIElementEvent } from './wrapper/engagement/ui-element/event-builder';
29
+ import { isLegacyPageType } from './wrapper/types';
28
30
  import { pageFromPageView } from './wrapper/utils/page-from-page-view';
29
31
  import { buildLegacyViewArticleEvent } from './wrapper/view/article/event-builder';
30
32
  import { buildLegacyViewFrontpageEvent } from './wrapper/view/frontpage/event-builder';
@@ -114,6 +116,27 @@ export function unsubscribe(eventName, callback) {
114
116
  * @class Tracker
115
117
  */
116
118
  export default class Tracker {
119
+ async getCurrentTpaasPage() {
120
+ const { id, type, url } = await this.currentPage;
121
+ return {
122
+ // remove the SDRN prefix, it's not expected for TPaaS
123
+ pageId: id.startsWith('sdrn:') ? id.split(':').slice(-1)[0] : id,
124
+ // make sure the pageType is valid for TPaaS
125
+ pageType: isPageType(type) ? type : 'Page',
126
+ url,
127
+ };
128
+ }
129
+ async getCurrentLegacyPage() {
130
+ const { id, type, url } = await this.currentPage;
131
+ // make sure the pageType is valid for legacy events
132
+ const validatedType = isLegacyPageType(type) ? type : 'Page';
133
+ return {
134
+ // add the SDRN prefix if it's not there, legacy events expect the full SDRN format
135
+ id: id.startsWith('sdrn:') ? id : `sdrn:${this.state.providerId}:${validatedType.toLowerCase()}:${id}`,
136
+ type: validatedType,
137
+ url,
138
+ };
139
+ }
117
140
  /**
118
141
  * > [!WARNING]
119
142
  * >
@@ -132,12 +155,18 @@ export default class Tracker {
132
155
  // the tracking of the same element with both old and new events at the same time
133
156
  this.legacyVisibilityObservers = new Map();
134
157
  this.visibilityObservers = new Map();
135
- this.legacyCurrentPage = {
136
- id: 'undefined-id',
158
+ /**
159
+ * @private
160
+ *
161
+ * This is used as the source of truth for the current page's information, which is updated based on the View Page-level event
162
+ * 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.
163
+ * Legacy events can provide an id with SDRN, while TPaaS events don't need it
164
+ */
165
+ this.currentPage = {
166
+ id: 'unknown',
137
167
  type: 'Page',
138
- url: typeof window !== 'undefined' ? window.location.href : '',
168
+ url: isBrowser ? window.location.href : undefined,
139
169
  };
140
- this.currentPage = undefined;
141
170
  /**
142
171
  * Promise that resolves when a session ID becomes available
143
172
  * @private
@@ -149,7 +178,7 @@ export default class Tracker {
149
178
  */
150
179
  this.hasProxyListener = false;
151
180
  this.setPageDefaults = (pageViewEvent) => {
152
- this.legacyCurrentPage = pageFromPageView(pageViewEvent);
181
+ this.currentPage = pageFromPageView(pageViewEvent);
153
182
  if (pageViewEvent.origin) {
154
183
  const pageDefaults = { origin: pageViewEvent.origin };
155
184
  this.update(pageDefaults, true);
@@ -157,8 +186,8 @@ export default class Tracker {
157
186
  };
158
187
  this.setCurrentPage = (id, type) => {
159
188
  this.currentPage = {
160
- pageId: id,
161
- pageType: type,
189
+ id: id || 'unknown',
190
+ type: type || 'Page',
162
191
  url: isBrowser ? window.location.href : undefined,
163
192
  };
164
193
  };
@@ -400,7 +429,9 @@ export default class Tracker {
400
429
  return {
401
430
  name: exp.name || exp.id,
402
431
  variant: exp.variant || '',
403
- experimentSdrn: exp.id,
432
+ // nobody should be sending experiment id with an sdrn format, but in case they do
433
+ // we want to be able to handle it and not generate an invalid sdrn by adding another layer to it
434
+ experimentSdrn: exp.id.startsWith('sdrn:') ? exp.id : generateExperimentSDRN(exp.id, exp.platform),
404
435
  platform: exp.platform || '',
405
436
  testId: exp.custom?.testId,
406
437
  };
@@ -487,10 +518,11 @@ export default class Tracker {
487
518
  source: source || 'unknown',
488
519
  };
489
520
  }
490
- const [actor, consents, experiments] = await Promise.all([
521
+ const [actor, consents, experiments, currentPage] = await Promise.all([
491
522
  this.getActor(),
492
523
  this.getConsents(),
493
524
  this.adaptExperiments(this.builders.experiments),
525
+ this.getCurrentTpaasPage(),
494
526
  ]);
495
527
  const { environmentId, jwe } = parsePulseCookies();
496
528
  return {
@@ -501,28 +533,12 @@ export default class Tracker {
501
533
  consents: adaptConsentsToNewFormat(consents),
502
534
  providerId: this.state.providerId,
503
535
  pageviewId: this.state.pageViewId,
504
- currentPage: this.currentPage,
536
+ currentPage,
505
537
  experiments,
506
538
  environmentId,
507
539
  jwe,
508
540
  };
509
541
  }
510
- /**
511
- * Generate a generic TPaaS event with common builders output
512
- * The method should be used for experiments, not for real production work.
513
- * It doesn't have any real TPaaS schema associated with it.
514
- * The method doesn't queue or send the event, it just returns the merged object.
515
- * The method can be removed at any time in the future, so use it with caution.
516
- *
517
- * @param input - The input object to merge with the common builders output
518
- * @returns A promise that resolves to the merged object
519
- * @private
520
- * @category TPaaS Tracking
521
- */
522
- async generateGenericTpaasEvent(input) {
523
- const dependencies = await this.prepareTpaasEvent();
524
- return pulseMerge({}, input, getCommonBuildersOutput(new Date(), dependencies));
525
- }
526
542
  /**
527
543
  * This method extends the list of base experiments that will be automatically included in every event sent by the tracker.
528
544
  *
@@ -544,6 +560,9 @@ export default class Tracker {
544
560
  * @category Tracking
545
561
  */
546
562
  addExperiments(experiments) {
563
+ if (experiments.some((experiment) => experiment.id.startsWith('sdrn:'))) {
564
+ logger.warn('Pulse - addExperiments received an experiment id starting with "sdrn:"; pass the raw experiment id instead. The SDK will generate the sdrn automatically.');
565
+ }
547
566
  this.update({ experiments }, true);
548
567
  }
549
568
  /**
@@ -578,7 +597,7 @@ export default class Tracker {
578
597
  const viewUIElementInputProperties = buildEventFromElement(element);
579
598
  this.trackLegacyViewUIElement(viewUIElementInputProperties);
580
599
  }));
581
- this.legacyVisibilityObservers.get(selector)?.observe(selector);
600
+ this.legacyVisibilityObservers.get(selector)?.observe(selector, {});
582
601
  }
583
602
  /**
584
603
  * The `addUIElementVisibilityTracking` function provides an interface for sending tracking events when specific UI elements become visible within the viewport.
@@ -610,7 +629,38 @@ export default class Tracker {
610
629
  const eventInput = buildEventFromElement(element);
611
630
  this.trackImpressionUIElement(eventInput);
612
631
  }));
613
- this.visibilityObservers.get(selector)?.observe(selector);
632
+ this.visibilityObservers.get(selector)?.observe(selector, {});
633
+ }
634
+ /**
635
+ * The `addElementVisibilityObserver` function provides an interface for executing arbitrary code when specific DOM Elements become visible within the viewport.
636
+ * It is built on the `VisibilityObserver` class, which efficiently detects visibility changes in DOM elements.
637
+ * When an element becomes at least 2/3 visible, a callback is triggered, with the Element that invoked the callback call as argument.
638
+ * It leverages the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) for viewport tracking
639
+ * and the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to monitor lazy-loaded elements.
640
+ *
641
+ * Example usage:
642
+ *
643
+ * ```ts
644
+ * tracker.addElementVisibilityObserver('[data-pulse-entity-id]', (element) => {
645
+ * const eventInput = parseElementIntoEventInput(element); // user-defined function
646
+ * tracker.trackImpressionTeaser(eventInput); // can be anything actually, not necessarily a Teaser tracking call
647
+ * });
648
+ * ```
649
+ * @param selector
650
+ * @param callback
651
+ * @param options {ObserverOptions}
652
+ * @param options.threshold - A number between 0 and 1 indicating how much of the element should be visible to trigger
653
+ * the callback. Default is 0.67 (2/3 of the element).
654
+ */
655
+ addElementVisibilityObserver(selector, callback, options = {}) {
656
+ if (this.visibilityObservers.has(selector)) {
657
+ console.warn(`Cannot add UI Element visibility tracking for selector "${selector}" as it is already being tracked.`);
658
+ return;
659
+ }
660
+ this.visibilityObservers.set(selector, new VisibilityObserver((element) => {
661
+ callback(element);
662
+ }));
663
+ this.visibilityObservers.get(selector)?.observe(selector, options);
614
664
  }
615
665
  /**
616
666
  * The old trackViewArticle method from the Media Wrapper.
@@ -704,7 +754,7 @@ export default class Tracker {
704
754
  * @category Legacy Tracking
705
755
  */
706
756
  trackLegacyViewUIElement(inputProperties, trackOptions) {
707
- const viewUIElementEventData = buildLegacyViewUIElementEvent(inputProperties, this.legacyCurrentPage);
757
+ const viewUIElementEventData = buildLegacyViewUIElementEvent(inputProperties, this.getCurrentLegacyPage());
708
758
  return this.track('trackerEvent', viewUIElementEventData, trackOptions);
709
759
  }
710
760
  /**
@@ -715,7 +765,7 @@ export default class Tracker {
715
765
  * @category Legacy Tracking
716
766
  */
717
767
  trackLegacyClickUIElement(inputProperties, trackOptions) {
718
- const clickUIElementEventData = buildClickUIElementEvent(inputProperties, this.legacyCurrentPage);
768
+ const clickUIElementEventData = buildClickUIElementEvent(inputProperties, this.getCurrentLegacyPage());
719
769
  return this.track('trackerEvent', clickUIElementEventData, trackOptions);
720
770
  }
721
771
  /**
@@ -788,10 +838,9 @@ export default class Tracker {
788
838
  const result = this.sendTpaasEvent(event);
789
839
  if (shouldEnableLeaveTracking(options.leaveTracking)) {
790
840
  const leavePageCallback = async (leave) => {
791
- const { metrics, ...rest } = input; // metrics are not part of LeavePage schema, so we need to omit them
792
841
  await this.trackLeavePage({
793
842
  leave,
794
- ...rest,
843
+ ...input,
795
844
  });
796
845
  };
797
846
  addTpaasLeaveTracking({
@@ -862,12 +911,9 @@ export default class Tracker {
862
911
  const result = this.sendTpaasEvent(event);
863
912
  if (shouldEnableLeaveTracking(options.leaveTracking)) {
864
913
  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
914
  await this.trackLeaveArticle({
868
915
  leave,
869
916
  ...input,
870
- object: rest,
871
917
  });
872
918
  };
873
919
  addTpaasLeaveTracking({
@@ -977,6 +1023,15 @@ export default class Tracker {
977
1023
  }
978
1024
  return result;
979
1025
  }
1026
+ /**
1027
+ * @param input
1028
+ * @category TPaaS Tracking
1029
+ */
1030
+ async trackImpressionAdSlot(input) {
1031
+ const dependencies = await this.prepareTpaasEvent();
1032
+ const event = buildImpressionAdSlotEvent(input, dependencies);
1033
+ return this.sendTpaasEvent(event);
1034
+ }
980
1035
  /**
981
1036
  * @param input
982
1037
  * @category TPaaS Tracking
@@ -1035,10 +1090,9 @@ export default class Tracker {
1035
1090
  const result = this.sendTpaasEvent(event);
1036
1091
  if (shouldEnableLeaveTracking(options.leaveTracking)) {
1037
1092
  const leaveFrontpageCallback = async (leave) => {
1038
- const { metrics, ...rest } = input; // metrics are not part of LeaveFrontpage schema, so we need to omit them
1039
1093
  await this.trackLeaveFrontpage({
1040
1094
  leave,
1041
- ...rest,
1095
+ ...input,
1042
1096
  });
1043
1097
  };
1044
1098
  addTpaasLeaveTracking({
@@ -1114,12 +1168,9 @@ export default class Tracker {
1114
1168
  const result = this.sendTpaasEvent(event);
1115
1169
  if (shouldEnableLeaveTracking(options.leaveTracking)) {
1116
1170
  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
1171
  await this.trackLeaveLockedArticle({
1120
1172
  leave,
1121
1173
  ...input,
1122
- object: rest,
1123
1174
  });
1124
1175
  };
1125
1176
  addTpaasLeaveTracking({
@@ -1265,11 +1316,9 @@ export default class Tracker {
1265
1316
  const result = this.sendTpaasEvent(event);
1266
1317
  if (shouldEnableLeaveTracking(options.leaveTracking)) {
1267
1318
  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
1319
  await this.trackLeaveWeather({
1271
1320
  leave,
1272
- ...rest,
1321
+ ...input,
1273
1322
  });
1274
1323
  };
1275
1324
  addTpaasLeaveTracking({
@@ -1,4 +1,4 @@
1
- function generateExperimentSDRN(id, experimentationPlatform) {
1
+ export function generateExperimentSDRN(id, experimentationPlatform) {
2
2
  return `sdrn:${experimentationPlatform}:experiment:${id}`;
3
3
  }
4
4
  /**
@@ -18,6 +18,7 @@ export const TPAAS_ALLOWED_METHODS = [
18
18
  'trackEngagementVideoAd',
19
19
  'trackEngagementWidget',
20
20
  'trackCompletedHealthAction',
21
+ 'trackImpressionAdSlot',
21
22
  'trackImpressionForm',
22
23
  'trackImpressionHealthUIElement',
23
24
  'trackImpressionOffer',
@@ -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.6';
2
+ export const SDK_VERSION = '8.0.0-rc.8';
@@ -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
- // Initialize both observers and start watching for DOM changes
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: 2 / 3 });
51
+ }, { threshold });
50
52
  }
51
53
  // Create observer to detect DOM changes that may add/remove tracked elements
52
54
  setupMutationObserver() {
@@ -1 +1,4 @@
1
- export {};
1
+ const LEGACY_PAGE_TYPE_VALUES = ['Article', 'Listing', 'Frontpage', 'SalesPoster', 'Page', 'LoginPoster'];
2
+ export function isLegacyPageType(value) {
3
+ return LEGACY_PAGE_TYPE_VALUES.includes(value);
4
+ }
@@ -1,6 +1,5 @@
1
1
  import { type AdvertisingIdentifiers, type AdvertisingVendor } from '@schibsted/pulse-cis-sync';
2
- import { getCommonBuildersOutput, type InputParams } from '@schibsted/tpaas-event-builder';
3
- import { type IAnonymousViewUIElement, type ICompletedHealthAction, type IEngagementAudio, type IEngagementForm, type IEngagementHealthUIElement, type IEngagementOffer, type IEngagementTeaser, type IEngagementUIElement, type IEngagementVideo, type IEngagementVideoAd, type IEngagementWidget, type IImpressionForm, type IImpressionHealthUIElement, type IImpressionOffer, type IImpressionPlayer, type IImpressionTeaser, type IImpressionUIElement, type IImpressionWidget, type ILeave, type ILeaveArticle, type ILeaveAudioPage, type ILeaveError, type ILeaveFrontpage, type ILeaveLandingpage, type ILeaveListing, type ILeaveLockedArticle, type ILeaveLockedAudioPage, type ILeaveLockedVideoPage, type ILeavePage, type ILeaveSportsPage, type ILeaveVideoPage, type ILeaveWeather, type ITpaasEvent, type IViewArticle, type IViewAudioPage, type IViewError, type IViewFrontpage, type IViewHealthPage, type IViewLandingpage, type IViewListing, type IViewLockedArticle, type IViewLockedAudioPage, type IViewLockedVideoPage, type IViewPage, type IViewSportsPage, type IViewTitle, type IViewVideoPage, type IViewWeather } from '@schibsted/tpaas-schemas';
2
+ import { type IAnonymousViewUIElement, type ICompletedHealthAction, type IEngagementAudio, type IEngagementForm, type IEngagementHealthUIElement, type IEngagementOffer, type IEngagementTeaser, type IEngagementUIElement, type IEngagementVideo, type IEngagementVideoAd, type IEngagementWidget, type IImpressionAdSlot, type IImpressionForm, type IImpressionHealthUIElement, type IImpressionOffer, type IImpressionPlayer, type IImpressionTeaser, type IImpressionUIElement, type IImpressionWidget, type ILeave, type ILeaveArticle, type ILeaveAudioPage, type ILeaveError, type ILeaveFrontpage, type ILeaveLandingpage, type ILeaveListing, type ILeaveLockedArticle, type ILeaveLockedAudioPage, type ILeaveLockedVideoPage, type ILeavePage, type ILeaveSportsPage, type ILeaveVideoPage, type ILeaveWeather, type InputParams, type ITpaasEvent, type IViewArticle, type IViewAudioPage, type IViewError, type IViewFrontpage, type IViewHealthPage, type IViewLandingpage, type IViewListing, type IViewLockedArticle, type IViewLockedAudioPage, type IViewLockedVideoPage, type IViewPage, type IViewSportsPage, type IViewTitle, type IViewVideoPage, type IViewWeather } from '@schibsted/tpaas-schemas';
4
3
  import logger from 'loglevel';
5
4
  import type { ActorInput, Consents, ConsentsInput, DeviceInput, LocationInput, ObjectInput, OriginInput, ProviderInput, TrackerInput } from './builders';
6
5
  import { engagementEvent, identityEvent, routableEvent, trackerEvent } from './builders/events';
@@ -9,8 +8,9 @@ import type { Deferred, ResolvedInput } from './defer';
9
8
  import { type Listener } from './EventBus/EventBus';
10
9
  import type { LeaveEvent, LeaveEventBuilder, LeaveEventOptions } from './leaveTracking/types';
11
10
  import type { AnonymousEventInputTypesByTrackingMethod, EventInput, EventInputTypesByTrackingMethod, TrackViewEventOptions } from './types';
11
+ import { type ObserverOptions } from './visibility-observer';
12
12
  import type { ClickUIElementInputProperties } from './wrapper/engagement/ui-element/types';
13
- import type { BaseEventExperiment } from './wrapper/types';
13
+ import { type BaseEventExperiment } from './wrapper/types';
14
14
  import type { ViewArticleInputProperties } from './wrapper/view/article/types';
15
15
  import type { ViewFrontpageInputProperties } from './wrapper/view/frontpage/types';
16
16
  import type { ViewListingInputProperties } from './wrapper/view/listing/types';
@@ -273,8 +273,16 @@ export default class Tracker {
273
273
  private bookmark;
274
274
  private legacyVisibilityObservers;
275
275
  private visibilityObservers;
276
- private legacyCurrentPage;
276
+ /**
277
+ * @private
278
+ *
279
+ * This is used as the source of truth for the current page's information, which is updated based on the View Page-level event
280
+ * 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.
281
+ * Legacy events can provide an id with SDRN, while TPaaS events don't need it
282
+ */
277
283
  private currentPage;
284
+ private getCurrentTpaasPage;
285
+ private getCurrentLegacyPage;
278
286
  /**
279
287
  * Promise that resolves when a session ID becomes available
280
288
  * @private
@@ -380,19 +388,6 @@ export default class Tracker {
380
388
  * @category TPaaS Tracking
381
389
  */
382
390
  private prepareTpaasEvent;
383
- /**
384
- * Generate a generic TPaaS event with common builders output
385
- * The method should be used for experiments, not for real production work.
386
- * It doesn't have any real TPaaS schema associated with it.
387
- * The method doesn't queue or send the event, it just returns the merged object.
388
- * The method can be removed at any time in the future, so use it with caution.
389
- *
390
- * @param input - The input object to merge with the common builders output
391
- * @returns A promise that resolves to the merged object
392
- * @private
393
- * @category TPaaS Tracking
394
- */
395
- generateGenericTpaasEvent<T>(input: T): Promise<T & ReturnType<typeof getCommonBuildersOutput>>;
396
391
  /**
397
392
  * This method extends the list of base experiments that will be automatically included in every event sent by the tracker.
398
393
  *
@@ -460,6 +455,28 @@ export default class Tracker {
460
455
  * @category Tracking
461
456
  */
462
457
  addUIElementVisibilityTracking(selector: string, buildEventFromElement: (element: HTMLElement) => InputParams<IImpressionUIElement>): void;
458
+ /**
459
+ * The `addElementVisibilityObserver` function provides an interface for executing arbitrary code when specific DOM Elements become visible within the viewport.
460
+ * It is built on the `VisibilityObserver` class, which efficiently detects visibility changes in DOM elements.
461
+ * When an element becomes at least 2/3 visible, a callback is triggered, with the Element that invoked the callback call as argument.
462
+ * It leverages the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) for viewport tracking
463
+ * and the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to monitor lazy-loaded elements.
464
+ *
465
+ * Example usage:
466
+ *
467
+ * ```ts
468
+ * tracker.addElementVisibilityObserver('[data-pulse-entity-id]', (element) => {
469
+ * const eventInput = parseElementIntoEventInput(element); // user-defined function
470
+ * tracker.trackImpressionTeaser(eventInput); // can be anything actually, not necessarily a Teaser tracking call
471
+ * });
472
+ * ```
473
+ * @param selector
474
+ * @param callback
475
+ * @param options {ObserverOptions}
476
+ * @param options.threshold - A number between 0 and 1 indicating how much of the element should be visible to trigger
477
+ * the callback. Default is 0.67 (2/3 of the element).
478
+ */
479
+ addElementVisibilityObserver(selector: string, callback: (element: HTMLElement) => void, options?: ObserverOptions): void;
463
480
  private setPageDefaults;
464
481
  /**
465
482
  * The old trackViewArticle method from the Media Wrapper.
@@ -625,6 +642,11 @@ export default class Tracker {
625
642
  * @category TPaaS Tracking
626
643
  */
627
644
  trackViewError(input: InputParams<IViewError>, options: TrackViewEventOptions<IViewError>): Promise<unknown>;
645
+ /**
646
+ * @param input
647
+ * @category TPaaS Tracking
648
+ */
649
+ trackImpressionAdSlot(input: InputParams<IImpressionAdSlot>): Promise<unknown>;
628
650
  /**
629
651
  * @param input
630
652
  * @category TPaaS Tracking
@@ -1,4 +1,5 @@
1
1
  import type { TrackerEventInput } from './events';
2
+ export declare function generateExperimentSDRN(id?: string | number, experimentationPlatform?: string): string;
2
3
  /**
3
4
  * The necessary values to construct a valid "sdrn" for experiment.
4
5
  * Additional properties are allowed.
@@ -5,7 +5,6 @@ export { TrackerEventInput } from './builders/events-node';
5
5
  export * from './Tracker';
6
6
  export { EventInput } from './types';
7
7
  export { T as Tracker };
8
- export type { InputParams } from '@schibsted/tpaas-event-builder';
9
8
  export * from '@schibsted/tpaas-schemas';
10
9
  export type { IPulseTrackerCreateParams } from './createPulseTracker';
11
10
  export { createPulseTracker } from './createPulseTracker';
@@ -17,7 +17,7 @@ export declare const MESSAGE_TYPE_INVOKE = "pulse-tracker-proxy:invoke";
17
17
  export declare const MESSAGE_TYPE_RETURN = "pulse-tracker-proxy:return";
18
18
  export declare const MESSAGE_TYPE_RETURN_ERROR = "pulse-tracker-proxy:return-error";
19
19
  export declare const ALLOWED_METHODS: readonly ["track"];
20
- export declare const TPAAS_ALLOWED_METHODS: readonly ["trackAnonymousViewUIElement", "trackEngagementAudio", "trackEngagementForm", "trackEngagementHealthUIElement", "trackEngagementOffer", "trackEngagementTeaser", "trackEngagementUIElement", "trackEngagementVideo", "trackEngagementVideoAd", "trackEngagementWidget", "trackCompletedHealthAction", "trackImpressionForm", "trackImpressionHealthUIElement", "trackImpressionOffer", "trackImpressionPlayer", "trackImpressionTeaser", "trackImpressionUIElement", "trackImpressionWidget", "trackLeaveArticle", "trackLeaveAudioPage", "trackLeaveError", "trackLeaveFrontpage", "trackLeaveLandingpage", "trackLeaveListing", "trackLeaveLockedArticle", "trackLeaveLockedAudioPage", "trackLeaveLockedVideoPage", "trackLeavePage", "trackLeaveSportsPage", "trackLeaveVideoPage", "trackLeaveWeather", "trackViewArticle", "trackViewAudioPage", "trackViewError", "trackViewFrontpage", "trackViewHealthPage", "trackViewLandingpage", "trackViewListing", "trackViewLockedArticle", "trackViewLockedAudioPage", "trackViewLockedVideoPage", "trackViewPage", "trackViewSportsPage", "trackViewTitle", "trackViewVideoPage", "trackViewWeather", "trackLegacyViewArticle", "trackLegacyViewLoginPoster", "trackLegacyViewSalesPoster", "trackLegacyViewListing", "trackLegacyViewFrontpage", "trackLegacyViewPage", "trackLegacyViewUIElement", "trackLegacyClickUIElement"];
20
+ export declare const TPAAS_ALLOWED_METHODS: readonly ["trackAnonymousViewUIElement", "trackEngagementAudio", "trackEngagementForm", "trackEngagementHealthUIElement", "trackEngagementOffer", "trackEngagementTeaser", "trackEngagementUIElement", "trackEngagementVideo", "trackEngagementVideoAd", "trackEngagementWidget", "trackCompletedHealthAction", "trackImpressionAdSlot", "trackImpressionForm", "trackImpressionHealthUIElement", "trackImpressionOffer", "trackImpressionPlayer", "trackImpressionTeaser", "trackImpressionUIElement", "trackImpressionWidget", "trackLeaveArticle", "trackLeaveAudioPage", "trackLeaveError", "trackLeaveFrontpage", "trackLeaveLandingpage", "trackLeaveListing", "trackLeaveLockedArticle", "trackLeaveLockedAudioPage", "trackLeaveLockedVideoPage", "trackLeavePage", "trackLeaveSportsPage", "trackLeaveVideoPage", "trackLeaveWeather", "trackViewArticle", "trackViewAudioPage", "trackViewError", "trackViewFrontpage", "trackViewHealthPage", "trackViewLandingpage", "trackViewListing", "trackViewLockedArticle", "trackViewLockedAudioPage", "trackViewLockedVideoPage", "trackViewPage", "trackViewSportsPage", "trackViewTitle", "trackViewVideoPage", "trackViewWeather", "trackLegacyViewArticle", "trackLegacyViewLoginPoster", "trackLegacyViewSalesPoster", "trackLegacyViewListing", "trackLegacyViewFrontpage", "trackLegacyViewPage", "trackLegacyViewUIElement", "trackLegacyClickUIElement"];
21
21
  export declare function isPulseProxyMessage(message: unknown): message is Message;
22
22
  export type TrackInvocation = {
23
23
  method: 'track';
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "8.0.0-rc.6";
1
+ export declare const SDK_VERSION = "8.0.0-rc.8";
@@ -1,4 +1,7 @@
1
1
  type ComponentViewCallback = (node: HTMLElement) => void;
2
+ export interface ObserverOptions {
3
+ threshold?: number;
4
+ }
2
5
  /**
3
6
  * Observes visibility of elements matching a selector using MutationObserver and IntersectionObserver.
4
7
  */
@@ -16,9 +19,11 @@ export declare class VisibilityObserver {
16
19
  * Uses {@link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver | MutationObserver} to monitor the DOM for dynamically loaded content.
17
20
  * Callback is invoked only once per element.
18
21
  * @param selector The CSS selector for elements to track.
22
+ * @param options {ObserverOptions} Optional configuration for the observer.
23
+ * @param options.threshold Which ratio triggers the Observer callback.
19
24
  * @throws Error if called more than once.
20
25
  */
21
- observe(selector: string): void;
26
+ observe(selector: string, { threshold }: ObserverOptions): void;
22
27
  /**
23
28
  * @returns {HTMLElement[]} An array of elements that match the selector in the document.
24
29
  */
@@ -1,5 +1,7 @@
1
1
  type NavigationType = 'navigation' | 'alternative' | 'refresh' | 'unknown';
2
- type PageType = 'Article' | 'Listing' | 'Frontpage' | 'SalesPoster' | 'Page' | 'LoginPoster';
2
+ declare const LEGACY_PAGE_TYPE_VALUES: readonly ["Article", "Listing", "Frontpage", "SalesPoster", "Page", "LoginPoster"];
3
+ export declare function isLegacyPageType(value: string): value is PageType;
4
+ type PageType = (typeof LEGACY_PAGE_TYPE_VALUES)[number];
3
5
  type TargetCategory = 'Article' | 'Audio' | 'Video' | 'Weather' | 'Title' | 'other';
4
6
  type TargetType = 'Article' | 'ArticleReference' | 'ExternalContent';
5
7
  type SalesPosterType = 'Ip' | 'Login' | 'Paid' | 'Meter' | 'Personalised';