@guardian/commercial-core 0.0.0-beta-20250716121613

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 (111) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +45 -0
  3. package/dist/cjs/ad-sizes.d.ts +202 -0
  4. package/dist/cjs/ad-sizes.js +400 -0
  5. package/dist/cjs/breakpoint.d.ts +8 -0
  6. package/dist/cjs/breakpoint.js +10 -0
  7. package/dist/cjs/constants/ad-label-height.d.ts +4 -0
  8. package/dist/cjs/constants/ad-label-height.js +7 -0
  9. package/dist/cjs/constants/index.d.ts +3 -0
  10. package/dist/cjs/constants/index.js +9 -0
  11. package/dist/cjs/constants/prebid-timeout.d.ts +4 -0
  12. package/dist/cjs/constants/prebid-timeout.js +7 -0
  13. package/dist/cjs/constants/top-above-nav-height.d.ts +10 -0
  14. package/dist/cjs/constants/top-above-nav-height.js +48 -0
  15. package/dist/cjs/detect-ad-blocker.d.ts +12 -0
  16. package/dist/cjs/detect-ad-blocker.js +61 -0
  17. package/dist/cjs/event-timer.d.ts +103 -0
  18. package/dist/cjs/event-timer.js +204 -0
  19. package/dist/cjs/geo/country-code.d.ts +3 -0
  20. package/dist/cjs/geo/country-code.js +34 -0
  21. package/dist/cjs/geo/geo-utils.d.ts +11 -0
  22. package/dist/cjs/geo/geo-utils.js +31 -0
  23. package/dist/cjs/geo/get-locale.d.ts +8 -0
  24. package/dist/cjs/geo/get-locale.js +43 -0
  25. package/dist/cjs/global.d.ts +71 -0
  26. package/dist/cjs/global.js +2 -0
  27. package/dist/cjs/index.d.ts +13 -0
  28. package/dist/cjs/index.js +46 -0
  29. package/dist/cjs/messenger/post-message.d.ts +1 -0
  30. package/dist/cjs/messenger/post-message.js +7 -0
  31. package/dist/cjs/permutive.d.ts +9 -0
  32. package/dist/cjs/permutive.js +38 -0
  33. package/dist/cjs/send-commercial-metrics.d.ts +58 -0
  34. package/dist/cjs/send-commercial-metrics.js +209 -0
  35. package/dist/cjs/targeting/build-page-targeting.d.ts +47 -0
  36. package/dist/cjs/targeting/build-page-targeting.js +112 -0
  37. package/dist/cjs/targeting/content.d.ts +87 -0
  38. package/dist/cjs/targeting/content.js +76 -0
  39. package/dist/cjs/targeting/personalised.d.ts +83 -0
  40. package/dist/cjs/targeting/personalised.js +140 -0
  41. package/dist/cjs/targeting/pick-targeting-values.d.ts +25 -0
  42. package/dist/cjs/targeting/pick-targeting-values.js +47 -0
  43. package/dist/cjs/targeting/session.d.ts +111 -0
  44. package/dist/cjs/targeting/session.js +61 -0
  45. package/dist/cjs/targeting/shared.d.ts +156 -0
  46. package/dist/cjs/targeting/shared.js +28 -0
  47. package/dist/cjs/targeting/teads-eligibility.d.ts +2 -0
  48. package/dist/cjs/targeting/teads-eligibility.js +20 -0
  49. package/dist/cjs/targeting/types.d.ts +6 -0
  50. package/dist/cjs/targeting/types.js +2 -0
  51. package/dist/cjs/targeting/viewport.d.ts +48 -0
  52. package/dist/cjs/targeting/viewport.js +22 -0
  53. package/dist/cjs/targeting/youtube-ima.d.ts +12 -0
  54. package/dist/cjs/targeting/youtube-ima.js +76 -0
  55. package/dist/cjs/types.d.ts +426 -0
  56. package/dist/cjs/types.js +12 -0
  57. package/dist/esm/ad-sizes.d.ts +202 -0
  58. package/dist/esm/ad-sizes.js +390 -0
  59. package/dist/esm/breakpoint.d.ts +8 -0
  60. package/dist/esm/breakpoint.js +6 -0
  61. package/dist/esm/constants/ad-label-height.d.ts +4 -0
  62. package/dist/esm/constants/ad-label-height.js +4 -0
  63. package/dist/esm/constants/index.d.ts +3 -0
  64. package/dist/esm/constants/index.js +3 -0
  65. package/dist/esm/constants/prebid-timeout.d.ts +4 -0
  66. package/dist/esm/constants/prebid-timeout.js +4 -0
  67. package/dist/esm/constants/top-above-nav-height.d.ts +10 -0
  68. package/dist/esm/constants/top-above-nav-height.js +45 -0
  69. package/dist/esm/detect-ad-blocker.d.ts +12 -0
  70. package/dist/esm/detect-ad-blocker.js +58 -0
  71. package/dist/esm/event-timer.d.ts +103 -0
  72. package/dist/esm/event-timer.js +199 -0
  73. package/dist/esm/geo/country-code.d.ts +3 -0
  74. package/dist/esm/geo/country-code.js +31 -0
  75. package/dist/esm/geo/geo-utils.d.ts +11 -0
  76. package/dist/esm/geo/geo-utils.js +20 -0
  77. package/dist/esm/geo/get-locale.d.ts +8 -0
  78. package/dist/esm/geo/get-locale.js +38 -0
  79. package/dist/esm/global.d.ts +71 -0
  80. package/dist/esm/global.js +0 -0
  81. package/dist/esm/index.d.ts +13 -0
  82. package/dist/esm/index.js +10 -0
  83. package/dist/esm/messenger/post-message.d.ts +1 -0
  84. package/dist/esm/messenger/post-message.js +3 -0
  85. package/dist/esm/permutive.d.ts +9 -0
  86. package/dist/esm/permutive.js +33 -0
  87. package/dist/esm/send-commercial-metrics.d.ts +58 -0
  88. package/dist/esm/send-commercial-metrics.js +204 -0
  89. package/dist/esm/targeting/build-page-targeting.d.ts +47 -0
  90. package/dist/esm/targeting/build-page-targeting.js +108 -0
  91. package/dist/esm/targeting/content.d.ts +87 -0
  92. package/dist/esm/targeting/content.js +73 -0
  93. package/dist/esm/targeting/personalised.d.ts +83 -0
  94. package/dist/esm/targeting/personalised.js +137 -0
  95. package/dist/esm/targeting/pick-targeting-values.d.ts +25 -0
  96. package/dist/esm/targeting/pick-targeting-values.js +43 -0
  97. package/dist/esm/targeting/session.d.ts +111 -0
  98. package/dist/esm/targeting/session.js +57 -0
  99. package/dist/esm/targeting/shared.d.ts +156 -0
  100. package/dist/esm/targeting/shared.js +25 -0
  101. package/dist/esm/targeting/teads-eligibility.d.ts +2 -0
  102. package/dist/esm/targeting/teads-eligibility.js +17 -0
  103. package/dist/esm/targeting/types.d.ts +6 -0
  104. package/dist/esm/targeting/types.js +0 -0
  105. package/dist/esm/targeting/viewport.d.ts +48 -0
  106. package/dist/esm/targeting/viewport.js +19 -0
  107. package/dist/esm/targeting/youtube-ima.d.ts +12 -0
  108. package/dist/esm/targeting/youtube-ima.js +73 -0
  109. package/dist/esm/types.d.ts +426 -0
  110. package/dist/esm/types.js +10 -0
  111. package/package.json +65 -0
@@ -0,0 +1,58 @@
1
+ import type { ConnectionType } from './types';
2
+ type Metric = {
3
+ name: string;
4
+ value: number;
5
+ };
6
+ type Property = {
7
+ name: string;
8
+ value: string;
9
+ };
10
+ type TimedEvent = {
11
+ name: string;
12
+ ts: number;
13
+ };
14
+ type DurationEvent = {
15
+ name: string;
16
+ duration: number;
17
+ };
18
+ type EventProperties = {
19
+ type?: ConnectionType;
20
+ downlink?: number;
21
+ effectiveType?: string;
22
+ };
23
+ declare enum Endpoints {
24
+ CODE = "//performance-events.code.dev-guardianapis.com/commercial-metrics",
25
+ PROD = "//performance-events.guardianapis.com/commercial-metrics"
26
+ }
27
+ declare const checkConsent: () => Promise<boolean>;
28
+ /**
29
+ * A method to asynchronously send metrics after initialization.
30
+ */
31
+ declare function bypassCommercialMetricsSampling(): Promise<void>;
32
+ interface InitCommercialMetricsArgs {
33
+ pageViewId: string;
34
+ browserId: string | undefined;
35
+ isDev: boolean;
36
+ adBlockerInUse?: boolean;
37
+ sampling?: number;
38
+ }
39
+ /**
40
+ * A method to initialise metrics.
41
+ * Note: this is initialised in the frontend/DCR bundles, not the commercial bundle.
42
+ * @param init.pageViewId - identifies the page view. Usually available on `guardian.config.ophan.pageViewId`. Defaults to `null`
43
+ * @param init.browserId - identifies the browser. Usually available via `getCookie({ name: 'bwid' })`. Defaults to `null`
44
+ * @param init.isDev - used to determine whether to use CODE or PROD endpoints.
45
+ * @param init.adBlockerInUse - indicates whether or not an adblocker is being used.
46
+ * @param init.sampling - rate at which to sample commercial metrics - the default is to send for 1% of pageviews
47
+ */
48
+ declare function initCommercialMetrics({ pageViewId, browserId, isDev, adBlockerInUse, sampling, }: InitCommercialMetricsArgs): Promise<boolean>;
49
+ export declare const _: {
50
+ Endpoints: typeof Endpoints;
51
+ setEndpoint: (isDev: boolean) => Endpoints;
52
+ mapEventTimerPropertiesToString: (properties: Array<[string, string | number]>) => Property[];
53
+ roundTimeStamp: (events: TimedEvent[], measures: DurationEvent[]) => Metric[];
54
+ transformToObjectEntries: (eventTimerProperties: EventProperties) => Array<[string, string | number | undefined]>;
55
+ reset: () => void;
56
+ };
57
+ export type { Property, TimedEvent, Metric };
58
+ export { bypassCommercialMetricsSampling, initCommercialMetrics, checkConsent };
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkConsent = exports._ = void 0;
4
+ exports.bypassCommercialMetricsSampling = bypassCommercialMetricsSampling;
5
+ exports.initCommercialMetrics = initCommercialMetrics;
6
+ const libs_1 = require("@guardian/libs");
7
+ const event_timer_1 = require("./event-timer");
8
+ var Endpoints;
9
+ (function (Endpoints) {
10
+ Endpoints["CODE"] = "//performance-events.code.dev-guardianapis.com/commercial-metrics";
11
+ Endpoints["PROD"] = "//performance-events.guardianapis.com/commercial-metrics";
12
+ })(Endpoints || (Endpoints = {}));
13
+ let commercialMetricsPayload = {
14
+ page_view_id: undefined,
15
+ browser_id: undefined,
16
+ platform: 'NEXT_GEN',
17
+ metrics: [],
18
+ properties: [],
19
+ };
20
+ let devProperties = [];
21
+ let adBlockerProperties = [];
22
+ let endpoint;
23
+ const setEndpoint = (isDev) => (endpoint = isDev ? Endpoints.CODE : Endpoints.PROD);
24
+ const setDevProperties = (isDev) => (devProperties = isDev
25
+ ? [{ name: 'isDev', value: window.location.hostname }]
26
+ : []);
27
+ const setAdBlockerProperties = (adBlockerInUse) => {
28
+ adBlockerProperties =
29
+ adBlockerInUse !== undefined
30
+ ? [
31
+ {
32
+ name: 'adBlockerInUse',
33
+ value: adBlockerInUse.toString(),
34
+ },
35
+ ]
36
+ : [];
37
+ };
38
+ const transformToObjectEntries = (eventTimerProperties) => {
39
+ // Transforms object {key: value} pairs into an array of [key, value] arrays
40
+ return Object.entries(eventTimerProperties);
41
+ };
42
+ const mapEventTimerPropertiesToString = (properties) => {
43
+ return properties.map(([name, value]) => ({
44
+ name: String(name),
45
+ value: String(value),
46
+ }));
47
+ };
48
+ const roundTimeStamp = (events, measures) => {
49
+ const roundedEvents = events.map(({ name, ts }) => ({
50
+ name,
51
+ value: Math.ceil(ts),
52
+ }));
53
+ const roundedMeasures = measures.map(({ name, duration }) => ({
54
+ name,
55
+ value: Math.ceil(duration),
56
+ }));
57
+ return [...roundedEvents, ...roundedMeasures];
58
+ };
59
+ function sendMetrics() {
60
+ (0, libs_1.log)('commercial', 'About to send commercial metrics', commercialMetricsPayload);
61
+ void fetch(endpoint, {
62
+ method: 'POST',
63
+ body: JSON.stringify(commercialMetricsPayload),
64
+ keepalive: true,
65
+ cache: 'no-store',
66
+ mode: 'no-cors',
67
+ });
68
+ }
69
+ /**
70
+ * Gather how many times the user has experienced the “offline” event
71
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event
72
+ *
73
+ * This value should be fetched as late as possible in the page lifecycle,
74
+ * to get an accurate value.
75
+ *
76
+ * Relevant for an @guardian/open-journalism investigation.
77
+ */
78
+ const getOfflineCount = () => typeof window.guardian.offlineCount === 'number'
79
+ ? [
80
+ {
81
+ name: 'offlineCount',
82
+ value: window.guardian.offlineCount,
83
+ },
84
+ ]
85
+ : [];
86
+ /**
87
+ * Measures added with @guardian/libs’s `startPerformanceMeasure`
88
+ *
89
+ * Allows for more granular monitoring of web page performance.
90
+ */
91
+ const getPerformanceMeasures = (...teams) => (0, libs_1.getMeasures)(teams).map(({ detail: { subscription, name, action }, duration }) => ({
92
+ name: [subscription, name, action].filter(libs_1.isNonNullable).join('_'),
93
+ value: duration,
94
+ }));
95
+ function gatherMetricsOnPageUnload() {
96
+ // Assemble commercial properties and metrics
97
+ const eventTimer = event_timer_1.EventTimer.get();
98
+ const transformedEntries = transformToObjectEntries(eventTimer.properties);
99
+ const filteredEventTimerProperties = transformedEntries.filter((item) => typeof item[1] !== 'undefined');
100
+ const mappedEventTimerProperties = mapEventTimerPropertiesToString(filteredEventTimerProperties);
101
+ const properties = mappedEventTimerProperties
102
+ .concat(devProperties)
103
+ .concat(adBlockerProperties);
104
+ commercialMetricsPayload.properties = properties;
105
+ const metrics = roundTimeStamp(eventTimer.marks, eventTimer.measures)
106
+ .concat(getOfflineCount())
107
+ .concat(getPerformanceMeasures('dotcom'));
108
+ commercialMetricsPayload.metrics = metrics;
109
+ sendMetrics();
110
+ }
111
+ const listener = (e) => {
112
+ if (window.guardian.config.shouldSendCommercialMetrics) {
113
+ switch (e.type) {
114
+ case 'visibilitychange':
115
+ if (document.visibilityState === 'hidden') {
116
+ gatherMetricsOnPageUnload();
117
+ }
118
+ return;
119
+ case 'pagehide':
120
+ gatherMetricsOnPageUnload();
121
+ return;
122
+ }
123
+ }
124
+ };
125
+ const addVisibilityListeners = () => {
126
+ // Report all available metrics when the page is unloaded or in background.
127
+ window.addEventListener('visibilitychange', listener, { once: true });
128
+ // Safari does not reliably fire the `visibilitychange` on page unload.
129
+ window.addEventListener('pagehide', listener, { once: true });
130
+ };
131
+ const checkConsent = async () => {
132
+ const consentState = await (0, libs_1.onConsent)();
133
+ if (consentState.tcfv2) {
134
+ // TCFv2 mode - check for consent
135
+ const consents = consentState.tcfv2.consents;
136
+ const REQUIRED_CONSENTS = [7, 8];
137
+ return REQUIRED_CONSENTS.every((consent) => consents[consent]);
138
+ }
139
+ // non-TCFv2 mode - don't check for consent
140
+ return true;
141
+ };
142
+ exports.checkConsent = checkConsent;
143
+ /**
144
+ * A method to asynchronously send metrics after initialization.
145
+ */
146
+ async function bypassCommercialMetricsSampling() {
147
+ if (!window.guardian.config.commercialMetricsInitialised) {
148
+ console.warn('initCommercialMetrics not yet initialised');
149
+ return;
150
+ }
151
+ const consented = await checkConsent();
152
+ if (consented) {
153
+ window.guardian.config.shouldSendCommercialMetrics = true;
154
+ }
155
+ else {
156
+ (0, libs_1.log)('commercial', "Metrics won't be sent because consent wasn't given");
157
+ }
158
+ }
159
+ /**
160
+ * A method to initialise metrics.
161
+ * Note: this is initialised in the frontend/DCR bundles, not the commercial bundle.
162
+ * @param init.pageViewId - identifies the page view. Usually available on `guardian.config.ophan.pageViewId`. Defaults to `null`
163
+ * @param init.browserId - identifies the browser. Usually available via `getCookie({ name: 'bwid' })`. Defaults to `null`
164
+ * @param init.isDev - used to determine whether to use CODE or PROD endpoints.
165
+ * @param init.adBlockerInUse - indicates whether or not an adblocker is being used.
166
+ * @param init.sampling - rate at which to sample commercial metrics - the default is to send for 1% of pageviews
167
+ */
168
+ async function initCommercialMetrics({ pageViewId, browserId, isDev, adBlockerInUse, sampling = 1 / 100, }) {
169
+ commercialMetricsPayload.page_view_id = pageViewId;
170
+ commercialMetricsPayload.browser_id = browserId;
171
+ setEndpoint(isDev);
172
+ setDevProperties(isDev);
173
+ setAdBlockerProperties(adBlockerInUse);
174
+ addVisibilityListeners();
175
+ if (window.guardian.config.commercialMetricsInitialised) {
176
+ return false;
177
+ }
178
+ window.guardian.config.commercialMetricsInitialised = true;
179
+ const userIsInSamplingGroup = Math.random() <= sampling;
180
+ if (isDev || userIsInSamplingGroup) {
181
+ const consented = await checkConsent();
182
+ if (consented) {
183
+ window.guardian.config.shouldSendCommercialMetrics = true;
184
+ return true;
185
+ }
186
+ (0, libs_1.log)('commercial', "Metrics won't be sent because consent wasn't given");
187
+ }
188
+ return false;
189
+ }
190
+ exports._ = {
191
+ Endpoints,
192
+ setEndpoint,
193
+ mapEventTimerPropertiesToString,
194
+ roundTimeStamp,
195
+ transformToObjectEntries,
196
+ reset: () => {
197
+ window.guardian.config.commercialMetricsInitialised = false;
198
+ window.guardian.config.shouldSendCommercialMetrics = false;
199
+ commercialMetricsPayload = {
200
+ page_view_id: undefined,
201
+ browser_id: undefined,
202
+ platform: 'NEXT_GEN',
203
+ metrics: [],
204
+ properties: [],
205
+ };
206
+ removeEventListener('visibilitychange', listener);
207
+ removeEventListener('pagehide', listener);
208
+ },
209
+ };
@@ -0,0 +1,47 @@
1
+ import type { Participations } from '@guardian/ab-core';
2
+ import type { ConsentState, CountryCode } from '@guardian/libs';
3
+ import type { AdManagerGroup, Frequency } from './personalised';
4
+ import type { SharedTargeting } from './shared';
5
+ import type { TrueOrFalse } from './types';
6
+ type PartialWithNulls<T> = {
7
+ [P in keyof T]?: T[P] | null;
8
+ };
9
+ type PageTargeting = PartialWithNulls<{
10
+ ab: string[];
11
+ af: 't';
12
+ amtgrp: AdManagerGroup;
13
+ at: string;
14
+ bp: 'mobile' | 'tablet' | 'desktop';
15
+ cc: CountryCode;
16
+ cmp_interaction: string;
17
+ consent_tcfv2: string;
18
+ dcre: TrueOrFalse;
19
+ fr: Frequency;
20
+ inskin: TrueOrFalse;
21
+ pa: TrueOrFalse;
22
+ permutive: string[];
23
+ pv: string;
24
+ rc: string;
25
+ rdp: string;
26
+ ref: string;
27
+ rp: 'dotcom-rendering' | 'dotcom-platform';
28
+ s: string;
29
+ sens: TrueOrFalse;
30
+ si: TrueOrFalse;
31
+ skinsize: 'l' | 's';
32
+ urlkw: string[];
33
+ vl: string;
34
+ allkw: string[];
35
+ [_: string]: string | string[];
36
+ } & SharedTargeting>;
37
+ declare const filterValues: (pageTargets: Record<string, unknown>) => Record<string, string | string[]>;
38
+ type BuildPageTargetingParams = {
39
+ adFree: boolean;
40
+ clientSideParticipations: Participations;
41
+ consentState: ConsentState;
42
+ isSignedIn?: boolean;
43
+ youtube?: boolean;
44
+ };
45
+ declare const buildPageTargeting: ({ adFree, clientSideParticipations, consentState, isSignedIn, youtube, }: BuildPageTargetingParams) => Record<string, string | string[]>;
46
+ export { buildPageTargeting, filterValues };
47
+ export type { PageTargeting };
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.filterValues = exports.buildPageTargeting = void 0;
4
+ const libs_1 = require("@guardian/libs");
5
+ const event_timer_1 = require("../event-timer");
6
+ const get_locale_1 = require("../geo/get-locale");
7
+ const content_1 = require("./content");
8
+ const personalised_1 = require("./personalised");
9
+ const session_1 = require("./session");
10
+ const shared_1 = require("./shared");
11
+ const viewport_1 = require("./viewport");
12
+ const filterValues = (pageTargets) => {
13
+ const filtered = {};
14
+ for (const key in pageTargets) {
15
+ const value = pageTargets[key];
16
+ if ((0, libs_1.isString)(value)) {
17
+ filtered[key] = value;
18
+ }
19
+ else if (Array.isArray(value) &&
20
+ value.length > 0 &&
21
+ value.every(libs_1.isString)) {
22
+ filtered[key] = value;
23
+ }
24
+ }
25
+ return filtered;
26
+ };
27
+ exports.filterValues = filterValues;
28
+ const lastPerformanceEntryIsNavigationType = () => {
29
+ if (!(0, event_timer_1.supportsPerformanceAPI)()) {
30
+ return false;
31
+ }
32
+ const navigationEvents = performance.getEntriesByType('navigation');
33
+ const lastNavigationEvent = navigationEvents[navigationEvents.length - 1];
34
+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType#navigation
35
+ return lastNavigationEvent?.entryType === 'navigation';
36
+ };
37
+ const referrerMatchesHost = (referrer) => {
38
+ if (!referrer) {
39
+ return false;
40
+ }
41
+ const referrerUrl = new URL(referrer);
42
+ return referrerUrl.hostname === window.location.hostname;
43
+ };
44
+ // A consentless friendly way of determining if this is the users first visit to the page
45
+ const isFirstVisit = (referrer) => {
46
+ if ((0, event_timer_1.supportsPerformanceAPI)() && !lastPerformanceEntryIsNavigationType()) {
47
+ return false;
48
+ }
49
+ return !referrerMatchesHost(referrer);
50
+ };
51
+ const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, isSignedIn = false, youtube = false, }) => {
52
+ const { page, isDotcomRendering } = window.guardian.config;
53
+ const adFreeTargeting = adFree ? { af: 't' } : {};
54
+ const sharedAdTargeting = page.sharedAdTargeting
55
+ ? (0, shared_1.getSharedTargeting)(page.sharedAdTargeting)
56
+ : {};
57
+ const contentTargeting = (0, content_1.getContentTargeting)({
58
+ webPublicationDate: page.webPublicationDate,
59
+ eligibleForDCR: page.dcrCouldRender,
60
+ path: `/${page.pageId}`,
61
+ renderingPlatform: isDotcomRendering
62
+ ? 'dotcom-rendering'
63
+ : 'dotcom-platform',
64
+ section: page.section,
65
+ sensitive: page.isSensitive,
66
+ videoLength: page.videoDuration,
67
+ keywords: sharedAdTargeting.k ?? [],
68
+ });
69
+ const referrer = document.referrer || '';
70
+ const sessionTargeting = (0, session_1.getSessionTargeting)({
71
+ adTest: (0, libs_1.getCookie)({ name: 'adtest', shouldMemoize: true }),
72
+ countryCode: (0, get_locale_1.getLocale)(),
73
+ isSignedIn,
74
+ pageViewId: window.guardian.config.ophan.pageViewId,
75
+ participations: {
76
+ clientSideParticipations,
77
+ serverSideParticipations: window.guardian.config.tests ?? {},
78
+ },
79
+ referrer,
80
+ });
81
+ const getViewport = () => {
82
+ return {
83
+ width: window.innerWidth || document.body.clientWidth || 0,
84
+ height: window.innerHeight || document.body.clientHeight || 0,
85
+ };
86
+ };
87
+ const viewportTargeting = (0, viewport_1.getViewportTargeting)({
88
+ viewPortWidth: getViewport().width,
89
+ cmpBannerWillShow: !libs_1.cmp.hasInitialised() || libs_1.cmp.willShowPrivacyMessageSync(),
90
+ });
91
+ const personalisedTargeting = (0, personalised_1.getPersonalisedTargeting)({
92
+ state: consentState,
93
+ youtube,
94
+ });
95
+ const consentlessTargeting = {};
96
+ if (!(0, libs_1.getConsentFor)('googletag', consentState)) {
97
+ consentlessTargeting.firstvisit = isFirstVisit(referrer) ? 't' : 'f';
98
+ }
99
+ const pageTargets = {
100
+ ...personalisedTargeting,
101
+ ...sharedAdTargeting,
102
+ ...adFreeTargeting,
103
+ ...contentTargeting,
104
+ ...sessionTargeting,
105
+ ...viewportTargeting,
106
+ ...consentlessTargeting,
107
+ };
108
+ // filter !(string | string[]) and empty values
109
+ const pageTargeting = filterValues(pageTargets);
110
+ return pageTargeting;
111
+ };
112
+ exports.buildPageTargeting = buildPageTargeting;
@@ -0,0 +1,87 @@
1
+ import type { SharedTargeting } from './shared';
2
+ import type { False, True } from './types';
3
+ declare const videoLengths: readonly ["25", "30", "60", "90", "120", "150", "180", "210", "240", "270", "300"];
4
+ /**
5
+ * Content Targeting comes from the server
6
+ *
7
+ * For a specific URL, it will only change on
8
+ * - a Composer/CAPI update
9
+ * - a rendering platform capability update
10
+ * - a main media update
11
+ * - a series tag update
12
+ * - a surge in page views per minute
13
+ *
14
+ */
15
+ type ContentTargeting = {
16
+ /**
17
+ * **D**ot**c**om-**r**endering **E**ligible - [see on Ad Manager][gam]
18
+ *
19
+ * Type: _Predefined_
20
+ *
21
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11958028
22
+ */
23
+ dcre: True | False;
24
+ /**
25
+ * **R**ecently Published **C**ontent - [see on Ad Manager][gam]
26
+ *
27
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=13845194
28
+ */
29
+ rc: string;
30
+ /**
31
+ * Rendering Platform - [see on Ad Manager][gam]
32
+ *
33
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11881005
34
+ */
35
+ rp: 'dotcom-rendering' | 'dotcom-platform';
36
+ /**
37
+ * Site **S**ection - [see on Ad Manager][gam]
38
+ *
39
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=173967
40
+ */
41
+ s: string;
42
+ /**
43
+ * **Sens**itive - [see on Ad Manager][gam]
44
+ *
45
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11654206
46
+ */
47
+ sens: True | False;
48
+ /**
49
+ * URL Keywords - [see on Ad Manager][gam]
50
+ *
51
+ * Type: _Dynamic_
52
+ *
53
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=12058265
54
+ */
55
+ urlkw: string[];
56
+ /**
57
+ * **V**ideo **L**ength - [see on Ad Manager][gam]
58
+ *
59
+ * Video.JS only (?)
60
+ *
61
+ * Type: _Predefined_
62
+ *
63
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=195087
64
+ */
65
+ vl: null | (typeof videoLengths)[number];
66
+ /**
67
+ * **All** **K**ey**w**ords - [see on Ad Manager][gam]
68
+ * This is a list of all keywords on the page, including the section and the URL keywords
69
+ * Type: _Dynamic_
70
+ *
71
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=13995840
72
+ */
73
+ allkw: string[];
74
+ };
75
+ type Content = {
76
+ eligibleForDCR: boolean;
77
+ path: SharedTargeting['url'];
78
+ renderingPlatform: ContentTargeting['rp'];
79
+ section: ContentTargeting['s'];
80
+ sensitive: boolean;
81
+ videoLength?: number;
82
+ webPublicationDate: number;
83
+ keywords: string[];
84
+ };
85
+ declare const getContentTargeting: ({ eligibleForDCR, path, renderingPlatform, section, sensitive, videoLength, webPublicationDate, keywords, }: Content) => ContentTargeting;
86
+ export { getContentTargeting };
87
+ export type { ContentTargeting };
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getContentTargeting = void 0;
4
+ const libs_1 = require("@guardian/libs");
5
+ /* -- Types -- */
6
+ const videoLengths = [
7
+ '25', // TODO: confirm this is a real value
8
+ '30',
9
+ '60',
10
+ '90',
11
+ '120',
12
+ '150',
13
+ '180',
14
+ '210',
15
+ '240',
16
+ '270',
17
+ '300',
18
+ ];
19
+ /* -- Methods -- */
20
+ const getVideoLength = (videoLength) => {
21
+ const index = Math.min(Math.ceil(videoLength / 30), 10);
22
+ return videoLengths[index] ?? null;
23
+ };
24
+ const getUrlKeywords = (url) => {
25
+ const lastSegment = url
26
+ .split('/')
27
+ .filter(Boolean) // This handles a trailing slash
28
+ .slice(-1)[0];
29
+ return (0, libs_1.isString)(lastSegment) ? lastSegment.split('-').filter(Boolean) : [];
30
+ };
31
+ const concatUnique = (a, b) => [
32
+ ...new Set([...a, ...b]),
33
+ ];
34
+ // "0" means content < 2 hours old
35
+ // "1" means content between 2 hours and 24 hours old.
36
+ // "2" means content between 24 hours and 3 days old
37
+ // "3" means content between 3 and 7 days old
38
+ // "4" means content between 7 days and 1 month old
39
+ // "5" means content between 1 and 10 months old
40
+ // "6" means content between 10 and 14 months old
41
+ // "7" means content more than 14 months old
42
+ const calculateRecentlyPublishedBucket = (webPublicationDate) => {
43
+ const now = Date.now();
44
+ const hoursSincePublication = (now - webPublicationDate) / 1000 / 60 / 60;
45
+ const daysSincePublication = hoursSincePublication / 24;
46
+ const monthsSincePublication = daysSincePublication / 30; // near enough for our purposes
47
+ if (hoursSincePublication < 2)
48
+ return '0';
49
+ if (hoursSincePublication < 24)
50
+ return '1';
51
+ if (daysSincePublication < 3)
52
+ return '2';
53
+ if (daysSincePublication < 7)
54
+ return '3';
55
+ if (daysSincePublication < 30)
56
+ return '4';
57
+ if (monthsSincePublication < 10)
58
+ return '5';
59
+ if (monthsSincePublication < 14)
60
+ return '6';
61
+ return '7';
62
+ };
63
+ const getContentTargeting = ({ eligibleForDCR, path, renderingPlatform, section, sensitive, videoLength, webPublicationDate, keywords, }) => {
64
+ const urlkw = getUrlKeywords(path);
65
+ return {
66
+ dcre: eligibleForDCR ? 't' : 'f',
67
+ rc: calculateRecentlyPublishedBucket(webPublicationDate),
68
+ rp: renderingPlatform,
69
+ s: section,
70
+ sens: sensitive ? 't' : 'f',
71
+ urlkw,
72
+ vl: videoLength ? getVideoLength(videoLength) : null,
73
+ allkw: concatUnique(urlkw, keywords),
74
+ };
75
+ };
76
+ exports.getContentTargeting = getContentTargeting;
@@ -0,0 +1,83 @@
1
+ import type { ConsentState, TCEventStatusCode } from '@guardian/libs';
2
+ import type { False, NotApplicable, True } from './types';
3
+ declare const frequency: readonly ["0", "1", "2", "3", "4", "5", "6-9", "10-15", "16-19", "20-29", "30plus"];
4
+ type Frequency = (typeof frequency)[number];
5
+ declare const adManagerGroups: readonly ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
6
+ type AdManagerGroup = (typeof adManagerGroups)[number];
7
+ /**
8
+ * Personalised Targeting requires user consent
9
+ *
10
+ * It allows or prevents personalised advertising, restrict data processing
11
+ * and handles access to cookies and local storage
12
+ */
13
+ type PersonalisedTargeting = {
14
+ /**
15
+ * **A**d **M**anager **T**argeting **Gr**ou**p** – [see on Ad Manager][gam]
16
+ *
17
+ * Type: _Predefined_
18
+ *
19
+ * Sample values:
20
+ * -
21
+ *
22
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=12318099
23
+ * */
24
+ amtgrp: AdManagerGroup | null;
25
+ /**
26
+ * Interaction with TCFv2 banner – [see on Ad Manager][gam]
27
+ *
28
+ * Type: _Predefined_
29
+ *
30
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=12083384
31
+ */
32
+ cmp_interaction?: TCEventStatusCode | NotApplicable;
33
+ /**
34
+ * **TCFv2 Consent** to [all purposes] – [see on Ad Manager][gam]
35
+ *
36
+ * Type: _Predefined_
37
+ *
38
+ * [all purposes]: https://vendor-list.consensu.org/v2/vendor-list.json
39
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=12080297
40
+ * */
41
+ consent_tcfv2: True | False | NotApplicable;
42
+ /**
43
+ * **Fr**equency – [see on Ad Manager][gam]
44
+ *
45
+ * Type: _Predefined_
46
+ *
47
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=214647
48
+ */
49
+ fr: Frequency;
50
+ /**
51
+ * **P**ersonalised **A**ds Consent – [see on Ad Manager][gam]
52
+ *
53
+ * Type: _Predefined_
54
+ *
55
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11701767
56
+ */
57
+ pa: True | False;
58
+ /**
59
+ * **Permutive** user segments – [see on Ad Manager][gam]
60
+ *
61
+ * Type: _Predefined_
62
+ *
63
+ * Values: 900+ number IDs
64
+ *
65
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11958727
66
+ */
67
+ permutive: string[];
68
+ /**
69
+ * **R**estrict **D**ata **P**rocessing Flag – [see on Ad Manager][gam]
70
+ *
71
+ * Type: _Predefined_
72
+ *
73
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11701767
74
+ */
75
+ rdp: True | False | NotApplicable;
76
+ };
77
+ type Personalised = {
78
+ state: ConsentState;
79
+ youtube: boolean;
80
+ };
81
+ declare const getPersonalisedTargeting: ({ state, youtube, }: Personalised) => PersonalisedTargeting;
82
+ export { getPersonalisedTargeting };
83
+ export type { PersonalisedTargeting, AdManagerGroup, Frequency };