@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.
- package/LICENSE +201 -0
- package/README.md +45 -0
- package/dist/cjs/ad-sizes.d.ts +202 -0
- package/dist/cjs/ad-sizes.js +400 -0
- package/dist/cjs/breakpoint.d.ts +8 -0
- package/dist/cjs/breakpoint.js +10 -0
- package/dist/cjs/constants/ad-label-height.d.ts +4 -0
- package/dist/cjs/constants/ad-label-height.js +7 -0
- package/dist/cjs/constants/index.d.ts +3 -0
- package/dist/cjs/constants/index.js +9 -0
- package/dist/cjs/constants/prebid-timeout.d.ts +4 -0
- package/dist/cjs/constants/prebid-timeout.js +7 -0
- package/dist/cjs/constants/top-above-nav-height.d.ts +10 -0
- package/dist/cjs/constants/top-above-nav-height.js +48 -0
- package/dist/cjs/detect-ad-blocker.d.ts +12 -0
- package/dist/cjs/detect-ad-blocker.js +61 -0
- package/dist/cjs/event-timer.d.ts +103 -0
- package/dist/cjs/event-timer.js +204 -0
- package/dist/cjs/geo/country-code.d.ts +3 -0
- package/dist/cjs/geo/country-code.js +34 -0
- package/dist/cjs/geo/geo-utils.d.ts +11 -0
- package/dist/cjs/geo/geo-utils.js +31 -0
- package/dist/cjs/geo/get-locale.d.ts +8 -0
- package/dist/cjs/geo/get-locale.js +43 -0
- package/dist/cjs/global.d.ts +71 -0
- package/dist/cjs/global.js +2 -0
- package/dist/cjs/index.d.ts +13 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/messenger/post-message.d.ts +1 -0
- package/dist/cjs/messenger/post-message.js +7 -0
- package/dist/cjs/permutive.d.ts +9 -0
- package/dist/cjs/permutive.js +38 -0
- package/dist/cjs/send-commercial-metrics.d.ts +58 -0
- package/dist/cjs/send-commercial-metrics.js +209 -0
- package/dist/cjs/targeting/build-page-targeting.d.ts +47 -0
- package/dist/cjs/targeting/build-page-targeting.js +112 -0
- package/dist/cjs/targeting/content.d.ts +87 -0
- package/dist/cjs/targeting/content.js +76 -0
- package/dist/cjs/targeting/personalised.d.ts +83 -0
- package/dist/cjs/targeting/personalised.js +140 -0
- package/dist/cjs/targeting/pick-targeting-values.d.ts +25 -0
- package/dist/cjs/targeting/pick-targeting-values.js +47 -0
- package/dist/cjs/targeting/session.d.ts +111 -0
- package/dist/cjs/targeting/session.js +61 -0
- package/dist/cjs/targeting/shared.d.ts +156 -0
- package/dist/cjs/targeting/shared.js +28 -0
- package/dist/cjs/targeting/teads-eligibility.d.ts +2 -0
- package/dist/cjs/targeting/teads-eligibility.js +20 -0
- package/dist/cjs/targeting/types.d.ts +6 -0
- package/dist/cjs/targeting/types.js +2 -0
- package/dist/cjs/targeting/viewport.d.ts +48 -0
- package/dist/cjs/targeting/viewport.js +22 -0
- package/dist/cjs/targeting/youtube-ima.d.ts +12 -0
- package/dist/cjs/targeting/youtube-ima.js +76 -0
- package/dist/cjs/types.d.ts +426 -0
- package/dist/cjs/types.js +12 -0
- package/dist/esm/ad-sizes.d.ts +202 -0
- package/dist/esm/ad-sizes.js +390 -0
- package/dist/esm/breakpoint.d.ts +8 -0
- package/dist/esm/breakpoint.js +6 -0
- package/dist/esm/constants/ad-label-height.d.ts +4 -0
- package/dist/esm/constants/ad-label-height.js +4 -0
- package/dist/esm/constants/index.d.ts +3 -0
- package/dist/esm/constants/index.js +3 -0
- package/dist/esm/constants/prebid-timeout.d.ts +4 -0
- package/dist/esm/constants/prebid-timeout.js +4 -0
- package/dist/esm/constants/top-above-nav-height.d.ts +10 -0
- package/dist/esm/constants/top-above-nav-height.js +45 -0
- package/dist/esm/detect-ad-blocker.d.ts +12 -0
- package/dist/esm/detect-ad-blocker.js +58 -0
- package/dist/esm/event-timer.d.ts +103 -0
- package/dist/esm/event-timer.js +199 -0
- package/dist/esm/geo/country-code.d.ts +3 -0
- package/dist/esm/geo/country-code.js +31 -0
- package/dist/esm/geo/geo-utils.d.ts +11 -0
- package/dist/esm/geo/geo-utils.js +20 -0
- package/dist/esm/geo/get-locale.d.ts +8 -0
- package/dist/esm/geo/get-locale.js +38 -0
- package/dist/esm/global.d.ts +71 -0
- package/dist/esm/global.js +0 -0
- package/dist/esm/index.d.ts +13 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/messenger/post-message.d.ts +1 -0
- package/dist/esm/messenger/post-message.js +3 -0
- package/dist/esm/permutive.d.ts +9 -0
- package/dist/esm/permutive.js +33 -0
- package/dist/esm/send-commercial-metrics.d.ts +58 -0
- package/dist/esm/send-commercial-metrics.js +204 -0
- package/dist/esm/targeting/build-page-targeting.d.ts +47 -0
- package/dist/esm/targeting/build-page-targeting.js +108 -0
- package/dist/esm/targeting/content.d.ts +87 -0
- package/dist/esm/targeting/content.js +73 -0
- package/dist/esm/targeting/personalised.d.ts +83 -0
- package/dist/esm/targeting/personalised.js +137 -0
- package/dist/esm/targeting/pick-targeting-values.d.ts +25 -0
- package/dist/esm/targeting/pick-targeting-values.js +43 -0
- package/dist/esm/targeting/session.d.ts +111 -0
- package/dist/esm/targeting/session.js +57 -0
- package/dist/esm/targeting/shared.d.ts +156 -0
- package/dist/esm/targeting/shared.js +25 -0
- package/dist/esm/targeting/teads-eligibility.d.ts +2 -0
- package/dist/esm/targeting/teads-eligibility.js +17 -0
- package/dist/esm/targeting/types.d.ts +6 -0
- package/dist/esm/targeting/types.js +0 -0
- package/dist/esm/targeting/viewport.d.ts +48 -0
- package/dist/esm/targeting/viewport.js +19 -0
- package/dist/esm/targeting/youtube-ima.d.ts +12 -0
- package/dist/esm/targeting/youtube-ima.js +73 -0
- package/dist/esm/types.d.ts +426 -0
- package/dist/esm/types.js +10 -0
- 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 };
|