@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
@@ -53,6 +53,7 @@ const consent_manager_1 = require("./builders/consent-manager");
53
53
  const consents_1 = require("./builders/consents");
54
54
  const events_1 = require("./builders/events");
55
55
  const events_anonymous_node_1 = require("./builders/events-anonymous-node");
56
+ const experiments_1 = require("./builders/experiments");
56
57
  const session_browser_1 = require("./builders/session-browser");
57
58
  const config_1 = require("./config");
58
59
  const config_browser_1 = require("./config-browser");
@@ -68,6 +69,7 @@ const version_1 = require("./version");
68
69
  const visibility_observer_1 = require("./visibility-observer");
69
70
  const warnOnce_1 = __importDefault(require("./warnOnce"));
70
71
  const event_builder_1 = require("./wrapper/engagement/ui-element/event-builder");
72
+ const types_1 = require("./wrapper/types");
71
73
  const page_from_page_view_1 = require("./wrapper/utils/page-from-page-view");
72
74
  const event_builder_2 = require("./wrapper/view/article/event-builder");
73
75
  const event_builder_3 = require("./wrapper/view/frontpage/event-builder");
@@ -157,6 +159,27 @@ function unsubscribe(eventName, callback) {
157
159
  * @class Tracker
158
160
  */
159
161
  class Tracker {
162
+ async getCurrentTpaasPage() {
163
+ const { id, type, url } = await this.currentPage;
164
+ return {
165
+ // remove the SDRN prefix, it's not expected for TPaaS
166
+ pageId: id.startsWith('sdrn:') ? id.split(':').slice(-1)[0] : id,
167
+ // make sure the pageType is valid for TPaaS
168
+ pageType: (0, tpaas_event_builder_1.isPageType)(type) ? type : 'Page',
169
+ url,
170
+ };
171
+ }
172
+ async getCurrentLegacyPage() {
173
+ const { id, type, url } = await this.currentPage;
174
+ // make sure the pageType is valid for legacy events
175
+ const validatedType = (0, types_1.isLegacyPageType)(type) ? type : 'Page';
176
+ return {
177
+ // add the SDRN prefix if it's not there, legacy events expect the full SDRN format
178
+ id: id.startsWith('sdrn:') ? id : `sdrn:${this.state.providerId}:${validatedType.toLowerCase()}:${id}`,
179
+ type: validatedType,
180
+ url,
181
+ };
182
+ }
160
183
  /**
161
184
  * > [!WARNING]
162
185
  * >
@@ -175,12 +198,18 @@ class Tracker {
175
198
  // the tracking of the same element with both old and new events at the same time
176
199
  this.legacyVisibilityObservers = new Map();
177
200
  this.visibilityObservers = new Map();
178
- this.legacyCurrentPage = {
179
- id: 'undefined-id',
201
+ /**
202
+ * @private
203
+ *
204
+ * This is used as the source of truth for the current page's information, which is updated based on the View Page-level event
205
+ * 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.
206
+ * Legacy events can provide an id with SDRN, while TPaaS events don't need it
207
+ */
208
+ this.currentPage = {
209
+ id: 'unknown',
180
210
  type: 'Page',
181
- url: typeof window !== 'undefined' ? window.location.href : '',
211
+ url: pulse_utils_1.isBrowser ? window.location.href : undefined,
182
212
  };
183
- this.currentPage = undefined;
184
213
  /**
185
214
  * Promise that resolves when a session ID becomes available
186
215
  * @private
@@ -192,7 +221,7 @@ class Tracker {
192
221
  */
193
222
  this.hasProxyListener = false;
194
223
  this.setPageDefaults = (pageViewEvent) => {
195
- this.legacyCurrentPage = (0, page_from_page_view_1.pageFromPageView)(pageViewEvent);
224
+ this.currentPage = (0, page_from_page_view_1.pageFromPageView)(pageViewEvent);
196
225
  if (pageViewEvent.origin) {
197
226
  const pageDefaults = { origin: pageViewEvent.origin };
198
227
  this.update(pageDefaults, true);
@@ -200,8 +229,8 @@ class Tracker {
200
229
  };
201
230
  this.setCurrentPage = (id, type) => {
202
231
  this.currentPage = {
203
- pageId: id,
204
- pageType: type,
232
+ id: id || 'unknown',
233
+ type: type || 'Page',
205
234
  url: pulse_utils_1.isBrowser ? window.location.href : undefined,
206
235
  };
207
236
  };
@@ -443,7 +472,9 @@ class Tracker {
443
472
  return {
444
473
  name: exp.name || exp.id,
445
474
  variant: exp.variant || '',
446
- experimentSdrn: exp.id,
475
+ // nobody should be sending experiment id with an sdrn format, but in case they do
476
+ // we want to be able to handle it and not generate an invalid sdrn by adding another layer to it
477
+ experimentSdrn: exp.id.startsWith('sdrn:') ? exp.id : (0, experiments_1.generateExperimentSDRN)(exp.id, exp.platform),
447
478
  platform: exp.platform || '',
448
479
  testId: exp.custom?.testId,
449
480
  };
@@ -530,10 +561,11 @@ class Tracker {
530
561
  source: source || 'unknown',
531
562
  };
532
563
  }
533
- const [actor, consents, experiments] = await Promise.all([
564
+ const [actor, consents, experiments, currentPage] = await Promise.all([
534
565
  this.getActor(),
535
566
  this.getConsents(),
536
567
  this.adaptExperiments(this.builders.experiments),
568
+ this.getCurrentTpaasPage(),
537
569
  ]);
538
570
  const { environmentId, jwe } = (0, pulse_cis_sync_1.parsePulseCookies)();
539
571
  return {
@@ -544,28 +576,12 @@ class Tracker {
544
576
  consents: adaptConsentsToNewFormat(consents),
545
577
  providerId: this.state.providerId,
546
578
  pageviewId: this.state.pageViewId,
547
- currentPage: this.currentPage,
579
+ currentPage,
548
580
  experiments,
549
581
  environmentId,
550
582
  jwe,
551
583
  };
552
584
  }
553
- /**
554
- * Generate a generic TPaaS event with common builders output
555
- * The method should be used for experiments, not for real production work.
556
- * It doesn't have any real TPaaS schema associated with it.
557
- * The method doesn't queue or send the event, it just returns the merged object.
558
- * The method can be removed at any time in the future, so use it with caution.
559
- *
560
- * @param input - The input object to merge with the common builders output
561
- * @returns A promise that resolves to the merged object
562
- * @private
563
- * @category TPaaS Tracking
564
- */
565
- async generateGenericTpaasEvent(input) {
566
- const dependencies = await this.prepareTpaasEvent();
567
- return (0, pulse_utils_1.pulseMerge)({}, input, (0, tpaas_event_builder_1.getCommonBuildersOutput)(new Date(), dependencies));
568
- }
569
585
  /**
570
586
  * This method extends the list of base experiments that will be automatically included in every event sent by the tracker.
571
587
  *
@@ -587,6 +603,9 @@ class Tracker {
587
603
  * @category Tracking
588
604
  */
589
605
  addExperiments(experiments) {
606
+ if (experiments.some((experiment) => experiment.id.startsWith('sdrn:'))) {
607
+ loglevel_1.default.warn('Pulse - addExperiments received an experiment id starting with "sdrn:"; pass the raw experiment id instead. The SDK will generate the sdrn automatically.');
608
+ }
590
609
  this.update({ experiments }, true);
591
610
  }
592
611
  /**
@@ -621,7 +640,7 @@ class Tracker {
621
640
  const viewUIElementInputProperties = buildEventFromElement(element);
622
641
  this.trackLegacyViewUIElement(viewUIElementInputProperties);
623
642
  }));
624
- this.legacyVisibilityObservers.get(selector)?.observe(selector);
643
+ this.legacyVisibilityObservers.get(selector)?.observe(selector, {});
625
644
  }
626
645
  /**
627
646
  * The `addUIElementVisibilityTracking` function provides an interface for sending tracking events when specific UI elements become visible within the viewport.
@@ -653,7 +672,38 @@ class Tracker {
653
672
  const eventInput = buildEventFromElement(element);
654
673
  this.trackImpressionUIElement(eventInput);
655
674
  }));
656
- this.visibilityObservers.get(selector)?.observe(selector);
675
+ this.visibilityObservers.get(selector)?.observe(selector, {});
676
+ }
677
+ /**
678
+ * The `addElementVisibilityObserver` function provides an interface for executing arbitrary code when specific DOM Elements become visible within the viewport.
679
+ * It is built on the `VisibilityObserver` class, which efficiently detects visibility changes in DOM elements.
680
+ * When an element becomes at least 2/3 visible, a callback is triggered, with the Element that invoked the callback call as argument.
681
+ * It leverages the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) for viewport tracking
682
+ * and the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to monitor lazy-loaded elements.
683
+ *
684
+ * Example usage:
685
+ *
686
+ * ```ts
687
+ * tracker.addElementVisibilityObserver('[data-pulse-entity-id]', (element) => {
688
+ * const eventInput = parseElementIntoEventInput(element); // user-defined function
689
+ * tracker.trackImpressionTeaser(eventInput); // can be anything actually, not necessarily a Teaser tracking call
690
+ * });
691
+ * ```
692
+ * @param selector
693
+ * @param callback
694
+ * @param options {ObserverOptions}
695
+ * @param options.threshold - A number between 0 and 1 indicating how much of the element should be visible to trigger
696
+ * the callback. Default is 0.67 (2/3 of the element).
697
+ */
698
+ addElementVisibilityObserver(selector, callback, options = {}) {
699
+ if (this.visibilityObservers.has(selector)) {
700
+ console.warn(`Cannot add UI Element visibility tracking for selector "${selector}" as it is already being tracked.`);
701
+ return;
702
+ }
703
+ this.visibilityObservers.set(selector, new visibility_observer_1.VisibilityObserver((element) => {
704
+ callback(element);
705
+ }));
706
+ this.visibilityObservers.get(selector)?.observe(selector, options);
657
707
  }
658
708
  /**
659
709
  * The old trackViewArticle method from the Media Wrapper.
@@ -747,7 +797,7 @@ class Tracker {
747
797
  * @category Legacy Tracking
748
798
  */
749
799
  trackLegacyViewUIElement(inputProperties, trackOptions) {
750
- const viewUIElementEventData = (0, event_builder_8.buildLegacyViewUIElementEvent)(inputProperties, this.legacyCurrentPage);
800
+ const viewUIElementEventData = (0, event_builder_8.buildLegacyViewUIElementEvent)(inputProperties, this.getCurrentLegacyPage());
751
801
  return this.track('trackerEvent', viewUIElementEventData, trackOptions);
752
802
  }
753
803
  /**
@@ -758,7 +808,7 @@ class Tracker {
758
808
  * @category Legacy Tracking
759
809
  */
760
810
  trackLegacyClickUIElement(inputProperties, trackOptions) {
761
- const clickUIElementEventData = (0, event_builder_1.buildClickUIElementEvent)(inputProperties, this.legacyCurrentPage);
811
+ const clickUIElementEventData = (0, event_builder_1.buildClickUIElementEvent)(inputProperties, this.getCurrentLegacyPage());
762
812
  return this.track('trackerEvent', clickUIElementEventData, trackOptions);
763
813
  }
764
814
  /**
@@ -831,10 +881,9 @@ class Tracker {
831
881
  const result = this.sendTpaasEvent(event);
832
882
  if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
833
883
  const leavePageCallback = async (leave) => {
834
- const { metrics, ...rest } = input; // metrics are not part of LeavePage schema, so we need to omit them
835
884
  await this.trackLeavePage({
836
885
  leave,
837
- ...rest,
886
+ ...input,
838
887
  });
839
888
  };
840
889
  (0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
@@ -905,12 +954,9 @@ class Tracker {
905
954
  const result = this.sendTpaasEvent(event);
906
955
  if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
907
956
  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
957
  await this.trackLeaveArticle({
911
958
  leave,
912
959
  ...input,
913
- object: rest,
914
960
  });
915
961
  };
916
962
  (0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
@@ -1020,6 +1066,15 @@ class Tracker {
1020
1066
  }
1021
1067
  return result;
1022
1068
  }
1069
+ /**
1070
+ * @param input
1071
+ * @category TPaaS Tracking
1072
+ */
1073
+ async trackImpressionAdSlot(input) {
1074
+ const dependencies = await this.prepareTpaasEvent();
1075
+ const event = (0, tpaas_event_builder_1.buildImpressionAdSlotEvent)(input, dependencies);
1076
+ return this.sendTpaasEvent(event);
1077
+ }
1023
1078
  /**
1024
1079
  * @param input
1025
1080
  * @category TPaaS Tracking
@@ -1078,10 +1133,9 @@ class Tracker {
1078
1133
  const result = this.sendTpaasEvent(event);
1079
1134
  if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
1080
1135
  const leaveFrontpageCallback = async (leave) => {
1081
- const { metrics, ...rest } = input; // metrics are not part of LeaveFrontpage schema, so we need to omit them
1082
1136
  await this.trackLeaveFrontpage({
1083
1137
  leave,
1084
- ...rest,
1138
+ ...input,
1085
1139
  });
1086
1140
  };
1087
1141
  (0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
@@ -1157,12 +1211,9 @@ class Tracker {
1157
1211
  const result = this.sendTpaasEvent(event);
1158
1212
  if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
1159
1213
  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
1214
  await this.trackLeaveLockedArticle({
1163
1215
  leave,
1164
1216
  ...input,
1165
- object: rest,
1166
1217
  });
1167
1218
  };
1168
1219
  (0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
@@ -1308,11 +1359,9 @@ class Tracker {
1308
1359
  const result = this.sendTpaasEvent(event);
1309
1360
  if ((0, leaveTrackingTpaas_1.shouldEnableLeaveTracking)(options.leaveTracking)) {
1310
1361
  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
1362
  await this.trackLeaveWeather({
1314
1363
  leave,
1315
- ...rest,
1364
+ ...input,
1316
1365
  });
1317
1366
  };
1318
1367
  (0, leaveTrackingTpaas_1.addTpaasLeaveTracking)({
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.defaultValue = void 0;
4
+ exports.generateExperimentSDRN = generateExperimentSDRN;
4
5
  exports.default = experiments;
5
6
  function generateExperimentSDRN(id, experimentationPlatform) {
6
7
  return `sdrn:${experimentationPlatform}:experiment:${id}`;
@@ -22,6 +22,7 @@ exports.TPAAS_ALLOWED_METHODS = [
22
22
  'trackEngagementVideoAd',
23
23
  'trackEngagementWidget',
24
24
  'trackCompletedHealthAction',
25
+ 'trackImpressionAdSlot',
25
26
  'trackImpressionForm',
26
27
  'trackImpressionHealthUIElement',
27
28
  'trackImpressionOffer',
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SDK_VERSION = void 0;
4
4
  // This file is auto-generated by bin/generate-version.js. Do not edit manually.
5
- exports.SDK_VERSION = '8.0.0-rc.6';
5
+ exports.SDK_VERSION = '8.0.0-rc.8';
@@ -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
- // Initialize both observers and start watching for DOM changes
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: 2 / 3 });
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
+ }