@guardian/commercial-core 0.31.0 → 0.34.1
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/README.md +2 -2
- package/dist/cjs/EventTimer.d.ts +2 -2
- package/dist/cjs/EventTimer.js +14 -14
- package/dist/cjs/detectAdBlocker.js +2 -1
- package/dist/cjs/targeting/content.d.ts +7 -127
- package/dist/cjs/targeting/content.js +4 -52
- 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.js +2 -1
- package/dist/cjs/targeting/shared.d.ts +142 -0
- package/dist/cjs/targeting/shared.js +58 -0
- package/dist/esm/EventTimer.d.ts +2 -2
- package/dist/esm/EventTimer.js +14 -14
- package/dist/esm/detectAdBlocker.js +2 -1
- package/dist/esm/targeting/content.d.ts +7 -127
- package/dist/esm/targeting/content.js +4 -52
- 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.js +2 -1
- package/dist/esm/targeting/shared.d.ts +142 -0
- package/dist/esm/targeting/shared.js +54 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
[](https://chat.google.com/room/AAAAPL2MBvE)
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
|
|
27
|
+
yarn add @guardian/commercial-core
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
or
|
|
@@ -44,7 +44,7 @@ If your target environment does not support that, make sure you transpile this p
|
|
|
44
44
|
### Requirements
|
|
45
45
|
|
|
46
46
|
1. [Node 14](https://nodejs.org/en/download/) ([nvm][] or [fnm][] recommended)
|
|
47
|
-
2. [
|
|
47
|
+
2. [Yarn](https://classic.yarnpkg.com/en/docs/install/)
|
|
48
48
|
|
|
49
49
|
[nvm]: https://github.com/nvm-sh/nvm
|
|
50
50
|
[fnm]: https://github.com/Schniz/fnm
|
package/dist/cjs/EventTimer.d.ts
CHANGED
|
@@ -58,7 +58,6 @@ export declare class EventTimer {
|
|
|
58
58
|
*/
|
|
59
59
|
get events(): Event[];
|
|
60
60
|
constructor();
|
|
61
|
-
mark(name: string): void;
|
|
62
61
|
/**
|
|
63
62
|
* Creates a new performance mark
|
|
64
63
|
* For slot events also ensures each TYPE of event event is marked only once for 'first'
|
|
@@ -68,6 +67,7 @@ export declare class EventTimer {
|
|
|
68
67
|
* @param {origin} [origin=page] - Either 'page' (default) or the name of the slot
|
|
69
68
|
*/
|
|
70
69
|
trigger(eventName: string, origin?: string): void;
|
|
71
|
-
|
|
70
|
+
private mark;
|
|
71
|
+
private trackInGA;
|
|
72
72
|
}
|
|
73
73
|
export {};
|
package/dist/cjs/EventTimer.js
CHANGED
|
@@ -102,20 +102,6 @@ class EventTimer {
|
|
|
102
102
|
]
|
|
103
103
|
: this._events;
|
|
104
104
|
}
|
|
105
|
-
mark(name) {
|
|
106
|
-
const longName = `gu.commercial.${name}`;
|
|
107
|
-
if (typeof window.performance !== 'undefined' &&
|
|
108
|
-
'mark' in window.performance) {
|
|
109
|
-
window.performance.mark(longName);
|
|
110
|
-
// Most recent mark with this name is the event we just created.
|
|
111
|
-
const mark = window.performance
|
|
112
|
-
.getEntriesByName(longName, 'mark')
|
|
113
|
-
.slice(-1)[0];
|
|
114
|
-
if (typeof mark !== 'undefined') {
|
|
115
|
-
this._events.push(new Event(name, mark));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
105
|
/**
|
|
120
106
|
* Creates a new performance mark
|
|
121
107
|
* For slot events also ensures each TYPE of event event is marked only once for 'first'
|
|
@@ -148,6 +134,20 @@ class EventTimer {
|
|
|
148
134
|
}
|
|
149
135
|
}
|
|
150
136
|
}
|
|
137
|
+
mark(name) {
|
|
138
|
+
const longName = `gu.commercial.${name}`;
|
|
139
|
+
if (typeof window.performance !== 'undefined' &&
|
|
140
|
+
'mark' in window.performance) {
|
|
141
|
+
window.performance.mark(longName);
|
|
142
|
+
// Most recent mark with this name is the event we just created.
|
|
143
|
+
const mark = window.performance
|
|
144
|
+
.getEntriesByName(longName, 'mark')
|
|
145
|
+
.slice(-1)[0];
|
|
146
|
+
if (typeof mark !== 'undefined') {
|
|
147
|
+
this._events.push(new Event(name, mark));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
151
|
trackInGA(eventName, label = '') {
|
|
152
152
|
const gaEvent = this.gaConfig.logEvents.find((e) => e.timingVariable === eventName);
|
|
153
153
|
if (gaEvent) {
|
|
@@ -16,8 +16,9 @@ function adElementBlocked(ad) {
|
|
|
16
16
|
ad.offsetTop === 0 ||
|
|
17
17
|
ad.offsetWidth === 0 ||
|
|
18
18
|
ad.clientHeight === 0 ||
|
|
19
|
-
ad.clientWidth === 0)
|
|
19
|
+
ad.clientWidth === 0) {
|
|
20
20
|
return true;
|
|
21
|
+
}
|
|
21
22
|
const adStyles = window.getComputedStyle(ad);
|
|
22
23
|
if (adStyles.getPropertyValue('display') === 'none')
|
|
23
24
|
return true;
|
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
import type { False, True } from '../types';
|
|
2
|
-
|
|
3
|
-
readonly Foundation: "f";
|
|
4
|
-
readonly Paid: "p";
|
|
5
|
-
readonly Sponsored: "s";
|
|
6
|
-
};
|
|
7
|
-
declare const editions: {
|
|
8
|
-
readonly UnitedKingdom: "uk";
|
|
9
|
-
readonly UnitedStates: "us";
|
|
10
|
-
readonly Australia: "au";
|
|
11
|
-
readonly International: "int";
|
|
12
|
-
};
|
|
2
|
+
import type { SharedTargeting } from './shared';
|
|
13
3
|
declare const videoLengths: readonly ["25", "30", "60", "90", "120", "150", "180", "210", "240", "270", "300"];
|
|
14
|
-
declare const surges: {
|
|
15
|
-
readonly 0: "0";
|
|
16
|
-
readonly 50: "5";
|
|
17
|
-
readonly 100: "4";
|
|
18
|
-
readonly 200: "3";
|
|
19
|
-
readonly 300: "2";
|
|
20
|
-
readonly 400: "1";
|
|
21
|
-
};
|
|
22
|
-
declare const platforms: {
|
|
23
|
-
readonly R2: "r2";
|
|
24
|
-
readonly NextGen: "ng";
|
|
25
|
-
readonly MobileApp: "app";
|
|
26
|
-
readonly AcceleratedMobilePages: "amp";
|
|
27
|
-
};
|
|
28
|
-
declare const contentTypes: readonly ["article", "audio", "crossword", "gallery", "interactive", "liveblog", "network-front", "section", "tag", "video"];
|
|
29
4
|
/**
|
|
30
5
|
* Content Targeting comes from the server
|
|
31
6
|
*
|
|
@@ -38,40 +13,6 @@ declare const contentTypes: readonly ["article", "audio", "crossword", "gallery"
|
|
|
38
13
|
*
|
|
39
14
|
*/
|
|
40
15
|
export declare type ContentTargeting = {
|
|
41
|
-
/**
|
|
42
|
-
* **Bl**og tags – [see on Ad Manager][gam]
|
|
43
|
-
*
|
|
44
|
-
* Type: _Dynamic_
|
|
45
|
-
*
|
|
46
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186687
|
|
47
|
-
*/
|
|
48
|
-
bl: string[];
|
|
49
|
-
/**
|
|
50
|
-
* **Br**anding - [see on Ad Manager][gam]
|
|
51
|
-
*
|
|
52
|
-
* Type: _Predefined_
|
|
53
|
-
*
|
|
54
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=259767
|
|
55
|
-
*/
|
|
56
|
-
br: typeof brands[keyof typeof brands] | null;
|
|
57
|
-
/**
|
|
58
|
-
* **Co**ntributor - [see on Ad Manager][gam]
|
|
59
|
-
*
|
|
60
|
-
* Array of all contributors to the content on the page
|
|
61
|
-
*
|
|
62
|
-
* Type: _Dynamic_
|
|
63
|
-
*
|
|
64
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186207
|
|
65
|
-
*/
|
|
66
|
-
co: string[];
|
|
67
|
-
/**
|
|
68
|
-
* **C**ontent **T**ype - [see on Ad Manager][gam]
|
|
69
|
-
*
|
|
70
|
-
* Type: _Predefined_
|
|
71
|
-
*
|
|
72
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177807
|
|
73
|
-
*/
|
|
74
|
-
ct: typeof contentTypes[number];
|
|
75
16
|
/**
|
|
76
17
|
* **D**ot**c**om-**r**endering **E**ligible - [see on Ad Manager][gam]
|
|
77
18
|
*
|
|
@@ -80,34 +21,6 @@ export declare type ContentTargeting = {
|
|
|
80
21
|
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11958028
|
|
81
22
|
*/
|
|
82
23
|
dcre: True | False;
|
|
83
|
-
/**
|
|
84
|
-
* **Edition** - [see on Ad Manager][gam]
|
|
85
|
-
*
|
|
86
|
-
* Type: _Predefined_
|
|
87
|
-
*
|
|
88
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174207
|
|
89
|
-
*/
|
|
90
|
-
edition: typeof editions[keyof typeof editions];
|
|
91
|
-
/**
|
|
92
|
-
* **K**eywords - [see on Ad Manager][gam]
|
|
93
|
-
*
|
|
94
|
-
* Type: _Dynamic_
|
|
95
|
-
*
|
|
96
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177687
|
|
97
|
-
*/
|
|
98
|
-
k: string[];
|
|
99
|
-
/**
|
|
100
|
-
* **Ob**server Content - [see on Ad Manager][gam]
|
|
101
|
-
*
|
|
102
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=256887
|
|
103
|
-
*/
|
|
104
|
-
ob: 't' | null;
|
|
105
|
-
/**
|
|
106
|
-
* **P**latform - [see on Ad Manager][gam]
|
|
107
|
-
*
|
|
108
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180207
|
|
109
|
-
*/
|
|
110
|
-
p: typeof platforms[keyof typeof platforms];
|
|
111
24
|
/**
|
|
112
25
|
* Rendering Platform - [see on Ad Manager][gam]
|
|
113
26
|
*
|
|
@@ -120,42 +33,12 @@ export declare type ContentTargeting = {
|
|
|
120
33
|
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=173967
|
|
121
34
|
*/
|
|
122
35
|
s: string;
|
|
123
|
-
/**
|
|
124
|
-
* **Se**ries - [see on Ad Manager][gam]
|
|
125
|
-
*
|
|
126
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180447
|
|
127
|
-
*/
|
|
128
|
-
se: string[];
|
|
129
36
|
/**
|
|
130
37
|
* **Sens**itive - [see on Ad Manager][gam]
|
|
131
38
|
*
|
|
132
39
|
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11654206
|
|
133
40
|
*/
|
|
134
41
|
sens: True | False;
|
|
135
|
-
/**
|
|
136
|
-
* **Su**rging Article - [see on Ad Manager][gam]
|
|
137
|
-
*
|
|
138
|
-
* Type: _Predefined_
|
|
139
|
-
*
|
|
140
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=185007
|
|
141
|
-
*/
|
|
142
|
-
su: Array<typeof surges[keyof typeof surges]>;
|
|
143
|
-
/**
|
|
144
|
-
* **T**o**n**es - [see on Ad Manager][gam]
|
|
145
|
-
*
|
|
146
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=191487
|
|
147
|
-
*/
|
|
148
|
-
tn: string[];
|
|
149
|
-
/**
|
|
150
|
-
* **U**niform **R**esource **L**ocator - [see on Ad Manager][gam]
|
|
151
|
-
*
|
|
152
|
-
* Relative to `www.theguardian.com`, starts with `/`
|
|
153
|
-
*
|
|
154
|
-
* Type: _Dynamic_
|
|
155
|
-
*
|
|
156
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174327
|
|
157
|
-
*/
|
|
158
|
-
url: `/${string}`;
|
|
159
42
|
/**
|
|
160
43
|
* URL Keywords - [see on Ad Manager][gam]
|
|
161
44
|
*
|
|
@@ -175,15 +58,12 @@ export declare type ContentTargeting = {
|
|
|
175
58
|
*/
|
|
176
59
|
vl: null | typeof videoLengths[number];
|
|
177
60
|
};
|
|
178
|
-
export declare const getContentTargeting: ({
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
61
|
+
export declare const getContentTargeting: ({ eligibleForDCR, path, renderingPlatform, section, sensitive, videoLength, }: {
|
|
62
|
+
eligibleForDCR: boolean;
|
|
63
|
+
path: SharedTargeting['url'];
|
|
64
|
+
renderingPlatform: ContentTargeting['rp'];
|
|
65
|
+
section: ContentTargeting['s'];
|
|
183
66
|
sensitive: boolean;
|
|
184
|
-
tones: ContentTargeting['tn'];
|
|
185
|
-
path: ContentTargeting['url'];
|
|
186
67
|
videoLength?: number | undefined;
|
|
187
|
-
|
|
188
|
-
}, targeting: Omit<ContentTargeting, 'br' | 'ct' | 'co' | 'p' | 'sens' | 'tn' | 'url' | 'urlkw' | 'vl' | 'su'>) => ContentTargeting;
|
|
68
|
+
}) => ContentTargeting;
|
|
189
69
|
export {};
|
|
@@ -3,17 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getContentTargeting = void 0;
|
|
4
4
|
const libs_1 = require("@guardian/libs");
|
|
5
5
|
/* -- Types -- */
|
|
6
|
-
const brands = {
|
|
7
|
-
Foundation: 'f',
|
|
8
|
-
Paid: 'p',
|
|
9
|
-
Sponsored: 's',
|
|
10
|
-
};
|
|
11
|
-
const editions = {
|
|
12
|
-
UnitedKingdom: 'uk',
|
|
13
|
-
UnitedStates: 'us',
|
|
14
|
-
Australia: 'au',
|
|
15
|
-
International: 'int',
|
|
16
|
-
};
|
|
17
6
|
const videoLengths = [
|
|
18
7
|
'25',
|
|
19
8
|
'30',
|
|
@@ -27,32 +16,6 @@ const videoLengths = [
|
|
|
27
16
|
'270',
|
|
28
17
|
'300',
|
|
29
18
|
];
|
|
30
|
-
const surges = {
|
|
31
|
-
0: '0',
|
|
32
|
-
50: '5',
|
|
33
|
-
100: '4',
|
|
34
|
-
200: '3',
|
|
35
|
-
300: '2',
|
|
36
|
-
400: '1',
|
|
37
|
-
};
|
|
38
|
-
const platforms = {
|
|
39
|
-
R2: 'r2',
|
|
40
|
-
NextGen: 'ng',
|
|
41
|
-
MobileApp: 'app',
|
|
42
|
-
AcceleratedMobilePages: 'amp',
|
|
43
|
-
};
|
|
44
|
-
const contentTypes = [
|
|
45
|
-
'article',
|
|
46
|
-
'audio',
|
|
47
|
-
'crossword',
|
|
48
|
-
'gallery',
|
|
49
|
-
'interactive',
|
|
50
|
-
'liveblog',
|
|
51
|
-
'network-front',
|
|
52
|
-
'section',
|
|
53
|
-
'tag',
|
|
54
|
-
'video',
|
|
55
|
-
];
|
|
56
19
|
/* -- Methods -- */
|
|
57
20
|
const getVideoLength = (videoLength) => {
|
|
58
21
|
const index = Math.min(Math.ceil(videoLength / 30), 10);
|
|
@@ -65,24 +28,13 @@ const getUrlKeywords = (url) => {
|
|
|
65
28
|
.slice(-1)[0];
|
|
66
29
|
return (0, libs_1.isString)(lastSegment) ? lastSegment.split('-').filter(Boolean) : [];
|
|
67
30
|
};
|
|
68
|
-
const getSurgingParam = (surging) => {
|
|
69
|
-
if (surging < 50 || isNaN(surging))
|
|
70
|
-
return ['0'];
|
|
71
|
-
const thresholds = [400, 300, 200, 100, 50];
|
|
72
|
-
return thresholds.filter((n) => n <= surging).map((s) => surges[s]);
|
|
73
|
-
};
|
|
74
31
|
/* -- Targeting -- */
|
|
75
|
-
const getContentTargeting = ({
|
|
32
|
+
const getContentTargeting = ({ eligibleForDCR, path, renderingPlatform, section, sensitive, videoLength, }) => {
|
|
76
33
|
return {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
ct: contentType,
|
|
81
|
-
p: platforms[platform],
|
|
34
|
+
dcre: eligibleForDCR ? 't' : 'f',
|
|
35
|
+
rp: renderingPlatform,
|
|
36
|
+
s: section,
|
|
82
37
|
sens: sensitive ? 't' : 'f',
|
|
83
|
-
tn: tones,
|
|
84
|
-
su: getSurgingParam(surging),
|
|
85
|
-
url: path,
|
|
86
38
|
urlkw: getUrlKeywords(path),
|
|
87
39
|
vl: videoLength ? getVideoLength(videoLength) : null,
|
|
88
40
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConditionalExcept } from 'type-fest';
|
|
2
|
+
declare type ValidTargetingObject<Base> = ConditionalExcept<Base, null | undefined | '' | readonly [] | readonly [''] | boolean | number>;
|
|
3
|
+
/**
|
|
4
|
+
* Picks only keys with targeting values from an object.
|
|
5
|
+
* A targeting values is defined as either:
|
|
6
|
+
* - a non-empty string
|
|
7
|
+
* - an array of non-empty strings
|
|
8
|
+
*
|
|
9
|
+
* If you object is read-only, you can safely access properties on the result.
|
|
10
|
+
* For example:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* dirty = {
|
|
14
|
+
* valid: 'real',
|
|
15
|
+
* invalid: undefined,
|
|
16
|
+
* } as const;
|
|
17
|
+
*
|
|
18
|
+
* clean = pickDefinedValues(dirty);
|
|
19
|
+
*
|
|
20
|
+
* // @ts-expect-error -- you can’t access this property
|
|
21
|
+
* clean.invalid
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const pickTargetingValues: <T extends Record<string, string | readonly string[] | undefined>>(obj: T) => import("type-fest").Except<T, NonNullable<{ [Key in keyof T]: T[Key] extends number | boolean | "" | readonly [] | readonly [""] | null | undefined ? Key : never; }[keyof T]>>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pickTargetingValues = void 0;
|
|
4
|
+
const libs_1 = require("@guardian/libs");
|
|
5
|
+
const isTargetingString = (string) => (0, libs_1.isString)(string) && string !== '';
|
|
6
|
+
const isTargetingArray = (array) => Array.isArray(array) && array.filter(isTargetingString).length > 0;
|
|
7
|
+
const isValidTargeting = (value) => {
|
|
8
|
+
if (isTargetingString(value))
|
|
9
|
+
return true;
|
|
10
|
+
if (isTargetingArray(value))
|
|
11
|
+
return true;
|
|
12
|
+
return false;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Picks only keys with targeting values from an object.
|
|
16
|
+
* A targeting values is defined as either:
|
|
17
|
+
* - a non-empty string
|
|
18
|
+
* - an array of non-empty strings
|
|
19
|
+
*
|
|
20
|
+
* If you object is read-only, you can safely access properties on the result.
|
|
21
|
+
* For example:
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* dirty = {
|
|
25
|
+
* valid: 'real',
|
|
26
|
+
* invalid: undefined,
|
|
27
|
+
* } as const;
|
|
28
|
+
*
|
|
29
|
+
* clean = pickDefinedValues(dirty);
|
|
30
|
+
*
|
|
31
|
+
* // @ts-expect-error -- you can’t access this property
|
|
32
|
+
* clean.invalid
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
const pickTargetingValues = (obj) => {
|
|
36
|
+
const initialValue = {};
|
|
37
|
+
return Object.entries(obj).reduce((valid, [key, value]) => {
|
|
38
|
+
if (isValidTargeting(value)) {
|
|
39
|
+
// @ts-expect-error -- isValidTargeting checks this
|
|
40
|
+
valid[key] = Array.isArray(value)
|
|
41
|
+
? value.filter(isTargetingString)
|
|
42
|
+
: value;
|
|
43
|
+
}
|
|
44
|
+
return valid;
|
|
45
|
+
}, initialValue);
|
|
46
|
+
};
|
|
47
|
+
exports.pickTargetingValues = pickTargetingValues;
|
|
@@ -44,8 +44,9 @@ const experimentsTargeting = ({ clientSideParticipations, serverSideParticipatio
|
|
|
44
44
|
const serverSideExperiments = Object.entries(serverSideParticipations)
|
|
45
45
|
.map((test) => testToParams(...test))
|
|
46
46
|
.filter(libs_1.isString);
|
|
47
|
-
if (clientSideExperiment.length + serverSideExperiments.length === 0)
|
|
47
|
+
if (clientSideExperiment.length + serverSideExperiments.length === 0) {
|
|
48
48
|
return null;
|
|
49
|
+
}
|
|
49
50
|
return [...clientSideExperiment, ...serverSideExperiments];
|
|
50
51
|
};
|
|
51
52
|
/* -- Targeting -- */
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
declare const brands: {
|
|
2
|
+
readonly Foundation: "f";
|
|
3
|
+
readonly Paid: "p";
|
|
4
|
+
readonly Sponsored: "s";
|
|
5
|
+
};
|
|
6
|
+
declare const contentTypes: readonly ["article", "audio", "crossword", "gallery", "interactive", "liveblog", "network-front", "section", "tag", "video"];
|
|
7
|
+
declare const editions: {
|
|
8
|
+
readonly UnitedKingdom: "uk";
|
|
9
|
+
readonly UnitedStates: "us";
|
|
10
|
+
readonly Australia: "au";
|
|
11
|
+
readonly International: "int";
|
|
12
|
+
};
|
|
13
|
+
declare const platforms: {
|
|
14
|
+
readonly R2: "r2";
|
|
15
|
+
readonly NextGen: "ng";
|
|
16
|
+
readonly MobileApp: "app";
|
|
17
|
+
readonly AcceleratedMobilePages: "amp";
|
|
18
|
+
};
|
|
19
|
+
declare const surges: {
|
|
20
|
+
readonly 0: "0";
|
|
21
|
+
readonly 50: "5";
|
|
22
|
+
readonly 100: "4";
|
|
23
|
+
readonly 200: "3";
|
|
24
|
+
readonly 300: "2";
|
|
25
|
+
readonly 400: "1";
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Shared Targeting is passed by `frontend` https://git.io/JDJ6W
|
|
29
|
+
*
|
|
30
|
+
* It is generated in `commercial-shared` https://git.io/JDJ62
|
|
31
|
+
*
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
export declare type SharedTargeting = {
|
|
36
|
+
/**
|
|
37
|
+
* **Bl**og tags – [see on Ad Manager][gam]
|
|
38
|
+
*
|
|
39
|
+
* Type: _Dynamic_
|
|
40
|
+
*
|
|
41
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186687
|
|
42
|
+
*/
|
|
43
|
+
bl: string[];
|
|
44
|
+
/**
|
|
45
|
+
* **Br**anding - [see on Ad Manager][gam]
|
|
46
|
+
*
|
|
47
|
+
* Type: _Predefined_
|
|
48
|
+
*
|
|
49
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=259767
|
|
50
|
+
*/
|
|
51
|
+
br: typeof brands[keyof typeof brands];
|
|
52
|
+
/**
|
|
53
|
+
* **Co**ntributors and Authors - [see on Ad Manager][gam]
|
|
54
|
+
*
|
|
55
|
+
* Array of all contributors to the content on the page
|
|
56
|
+
*
|
|
57
|
+
* Type: _Dynamic_
|
|
58
|
+
*
|
|
59
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186207
|
|
60
|
+
*/
|
|
61
|
+
co: string[];
|
|
62
|
+
/**
|
|
63
|
+
* **C**ontent **T**ype - [see on Ad Manager][gam]
|
|
64
|
+
*
|
|
65
|
+
* Type: _Predefined_
|
|
66
|
+
*
|
|
67
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177807
|
|
68
|
+
*/
|
|
69
|
+
ct: typeof contentTypes[number];
|
|
70
|
+
/**
|
|
71
|
+
* **Edition** - [see on Ad Manager][gam]
|
|
72
|
+
*
|
|
73
|
+
* Type: _Predefined_
|
|
74
|
+
*
|
|
75
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174207
|
|
76
|
+
*/
|
|
77
|
+
edition: typeof editions[keyof typeof editions];
|
|
78
|
+
/**
|
|
79
|
+
* **K**eywords - [see on Ad Manager][gam]
|
|
80
|
+
*
|
|
81
|
+
* Type: _Dynamic_
|
|
82
|
+
*
|
|
83
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177687
|
|
84
|
+
*/
|
|
85
|
+
k: string[];
|
|
86
|
+
/**
|
|
87
|
+
* **Ob**server Content - [see on Ad Manager][gam]
|
|
88
|
+
*
|
|
89
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=256887
|
|
90
|
+
*/
|
|
91
|
+
ob: 't';
|
|
92
|
+
/**
|
|
93
|
+
* **P**latform - [see on Ad Manager][gam]
|
|
94
|
+
*
|
|
95
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180207
|
|
96
|
+
*/
|
|
97
|
+
p: typeof platforms[keyof typeof platforms];
|
|
98
|
+
/**
|
|
99
|
+
* **Se**ries - [see on Ad Manager][gam]
|
|
100
|
+
*
|
|
101
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180447
|
|
102
|
+
*/
|
|
103
|
+
se: string[];
|
|
104
|
+
/**
|
|
105
|
+
* **Sh**ort URL - [see on Ad Manager][gam]
|
|
106
|
+
*
|
|
107
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=286047
|
|
108
|
+
*/
|
|
109
|
+
sh: `https://www.theguardian.com/p/${string}`;
|
|
110
|
+
/**
|
|
111
|
+
* **Su**rging Article - [see on Ad Manager][gam]
|
|
112
|
+
*
|
|
113
|
+
* Type: _Predefined_
|
|
114
|
+
*
|
|
115
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=185007
|
|
116
|
+
*/
|
|
117
|
+
su: Array<typeof surges[keyof typeof surges]>;
|
|
118
|
+
/**
|
|
119
|
+
* **T**o**n**es - [see on Ad Manager][gam]
|
|
120
|
+
*
|
|
121
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=191487
|
|
122
|
+
*/
|
|
123
|
+
tn: string[];
|
|
124
|
+
/**
|
|
125
|
+
* **U**niform **R**esource **L**ocator - [see on Ad Manager][gam]
|
|
126
|
+
*
|
|
127
|
+
* Relative to `www.theguardian.com`, starts with `/`
|
|
128
|
+
*
|
|
129
|
+
* Type: _Dynamic_
|
|
130
|
+
*
|
|
131
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174327
|
|
132
|
+
*/
|
|
133
|
+
url: `/${string}`;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* What goes in comes out
|
|
137
|
+
*/
|
|
138
|
+
export declare const getSharedTargeting: (shared: Partial<SharedTargeting>) => Partial<SharedTargeting>;
|
|
139
|
+
export declare const _: {
|
|
140
|
+
getSurgingParam: (surging: number) => SharedTargeting['su'];
|
|
141
|
+
};
|
|
142
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._ = exports.getSharedTargeting = void 0;
|
|
4
|
+
const pick_targeting_values_1 = require("./pick-targeting-values");
|
|
5
|
+
/* -- Types -- */
|
|
6
|
+
const brands = {
|
|
7
|
+
Foundation: 'f',
|
|
8
|
+
Paid: 'p',
|
|
9
|
+
Sponsored: 's',
|
|
10
|
+
};
|
|
11
|
+
const contentTypes = [
|
|
12
|
+
'article',
|
|
13
|
+
'audio',
|
|
14
|
+
'crossword',
|
|
15
|
+
'gallery',
|
|
16
|
+
'interactive',
|
|
17
|
+
'liveblog',
|
|
18
|
+
'network-front',
|
|
19
|
+
'section',
|
|
20
|
+
'tag',
|
|
21
|
+
'video',
|
|
22
|
+
];
|
|
23
|
+
const editions = {
|
|
24
|
+
UnitedKingdom: 'uk',
|
|
25
|
+
UnitedStates: 'us',
|
|
26
|
+
Australia: 'au',
|
|
27
|
+
International: 'int',
|
|
28
|
+
};
|
|
29
|
+
const platforms = {
|
|
30
|
+
R2: 'r2',
|
|
31
|
+
NextGen: 'ng',
|
|
32
|
+
MobileApp: 'app',
|
|
33
|
+
AcceleratedMobilePages: 'amp',
|
|
34
|
+
};
|
|
35
|
+
const surges = {
|
|
36
|
+
0: '0',
|
|
37
|
+
50: '5',
|
|
38
|
+
100: '4',
|
|
39
|
+
200: '3',
|
|
40
|
+
300: '2',
|
|
41
|
+
400: '1',
|
|
42
|
+
};
|
|
43
|
+
/* -- Methods -- */
|
|
44
|
+
const getSurgingParam = (surging) => {
|
|
45
|
+
if (surging < 50 || isNaN(surging))
|
|
46
|
+
return ['0'];
|
|
47
|
+
const thresholds = [400, 300, 200, 100, 50];
|
|
48
|
+
return thresholds.filter((n) => n <= surging).map((s) => surges[s]);
|
|
49
|
+
};
|
|
50
|
+
/* -- Targeting -- */
|
|
51
|
+
/**
|
|
52
|
+
* What goes in comes out
|
|
53
|
+
*/
|
|
54
|
+
const getSharedTargeting = (shared) => (0, pick_targeting_values_1.pickTargetingValues)(shared);
|
|
55
|
+
exports.getSharedTargeting = getSharedTargeting;
|
|
56
|
+
exports._ = {
|
|
57
|
+
getSurgingParam,
|
|
58
|
+
};
|
package/dist/esm/EventTimer.d.ts
CHANGED
|
@@ -58,7 +58,6 @@ export declare class EventTimer {
|
|
|
58
58
|
*/
|
|
59
59
|
get events(): Event[];
|
|
60
60
|
constructor();
|
|
61
|
-
mark(name: string): void;
|
|
62
61
|
/**
|
|
63
62
|
* Creates a new performance mark
|
|
64
63
|
* For slot events also ensures each TYPE of event event is marked only once for 'first'
|
|
@@ -68,6 +67,7 @@ export declare class EventTimer {
|
|
|
68
67
|
* @param {origin} [origin=page] - Either 'page' (default) or the name of the slot
|
|
69
68
|
*/
|
|
70
69
|
trigger(eventName: string, origin?: string): void;
|
|
71
|
-
|
|
70
|
+
private mark;
|
|
71
|
+
private trackInGA;
|
|
72
72
|
}
|
|
73
73
|
export {};
|
package/dist/esm/EventTimer.js
CHANGED
|
@@ -99,20 +99,6 @@ export class EventTimer {
|
|
|
99
99
|
]
|
|
100
100
|
: this._events;
|
|
101
101
|
}
|
|
102
|
-
mark(name) {
|
|
103
|
-
const longName = `gu.commercial.${name}`;
|
|
104
|
-
if (typeof window.performance !== 'undefined' &&
|
|
105
|
-
'mark' in window.performance) {
|
|
106
|
-
window.performance.mark(longName);
|
|
107
|
-
// Most recent mark with this name is the event we just created.
|
|
108
|
-
const mark = window.performance
|
|
109
|
-
.getEntriesByName(longName, 'mark')
|
|
110
|
-
.slice(-1)[0];
|
|
111
|
-
if (typeof mark !== 'undefined') {
|
|
112
|
-
this._events.push(new Event(name, mark));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
102
|
/**
|
|
117
103
|
* Creates a new performance mark
|
|
118
104
|
* For slot events also ensures each TYPE of event event is marked only once for 'first'
|
|
@@ -145,6 +131,20 @@ export class EventTimer {
|
|
|
145
131
|
}
|
|
146
132
|
}
|
|
147
133
|
}
|
|
134
|
+
mark(name) {
|
|
135
|
+
const longName = `gu.commercial.${name}`;
|
|
136
|
+
if (typeof window.performance !== 'undefined' &&
|
|
137
|
+
'mark' in window.performance) {
|
|
138
|
+
window.performance.mark(longName);
|
|
139
|
+
// Most recent mark with this name is the event we just created.
|
|
140
|
+
const mark = window.performance
|
|
141
|
+
.getEntriesByName(longName, 'mark')
|
|
142
|
+
.slice(-1)[0];
|
|
143
|
+
if (typeof mark !== 'undefined') {
|
|
144
|
+
this._events.push(new Event(name, mark));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
148
|
trackInGA(eventName, label = '') {
|
|
149
149
|
const gaEvent = this.gaConfig.logEvents.find((e) => e.timingVariable === eventName);
|
|
150
150
|
if (gaEvent) {
|
|
@@ -13,8 +13,9 @@ function adElementBlocked(ad) {
|
|
|
13
13
|
ad.offsetTop === 0 ||
|
|
14
14
|
ad.offsetWidth === 0 ||
|
|
15
15
|
ad.clientHeight === 0 ||
|
|
16
|
-
ad.clientWidth === 0)
|
|
16
|
+
ad.clientWidth === 0) {
|
|
17
17
|
return true;
|
|
18
|
+
}
|
|
18
19
|
const adStyles = window.getComputedStyle(ad);
|
|
19
20
|
if (adStyles.getPropertyValue('display') === 'none')
|
|
20
21
|
return true;
|
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
import type { False, True } from '../types';
|
|
2
|
-
|
|
3
|
-
readonly Foundation: "f";
|
|
4
|
-
readonly Paid: "p";
|
|
5
|
-
readonly Sponsored: "s";
|
|
6
|
-
};
|
|
7
|
-
declare const editions: {
|
|
8
|
-
readonly UnitedKingdom: "uk";
|
|
9
|
-
readonly UnitedStates: "us";
|
|
10
|
-
readonly Australia: "au";
|
|
11
|
-
readonly International: "int";
|
|
12
|
-
};
|
|
2
|
+
import type { SharedTargeting } from './shared';
|
|
13
3
|
declare const videoLengths: readonly ["25", "30", "60", "90", "120", "150", "180", "210", "240", "270", "300"];
|
|
14
|
-
declare const surges: {
|
|
15
|
-
readonly 0: "0";
|
|
16
|
-
readonly 50: "5";
|
|
17
|
-
readonly 100: "4";
|
|
18
|
-
readonly 200: "3";
|
|
19
|
-
readonly 300: "2";
|
|
20
|
-
readonly 400: "1";
|
|
21
|
-
};
|
|
22
|
-
declare const platforms: {
|
|
23
|
-
readonly R2: "r2";
|
|
24
|
-
readonly NextGen: "ng";
|
|
25
|
-
readonly MobileApp: "app";
|
|
26
|
-
readonly AcceleratedMobilePages: "amp";
|
|
27
|
-
};
|
|
28
|
-
declare const contentTypes: readonly ["article", "audio", "crossword", "gallery", "interactive", "liveblog", "network-front", "section", "tag", "video"];
|
|
29
4
|
/**
|
|
30
5
|
* Content Targeting comes from the server
|
|
31
6
|
*
|
|
@@ -38,40 +13,6 @@ declare const contentTypes: readonly ["article", "audio", "crossword", "gallery"
|
|
|
38
13
|
*
|
|
39
14
|
*/
|
|
40
15
|
export declare type ContentTargeting = {
|
|
41
|
-
/**
|
|
42
|
-
* **Bl**og tags – [see on Ad Manager][gam]
|
|
43
|
-
*
|
|
44
|
-
* Type: _Dynamic_
|
|
45
|
-
*
|
|
46
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186687
|
|
47
|
-
*/
|
|
48
|
-
bl: string[];
|
|
49
|
-
/**
|
|
50
|
-
* **Br**anding - [see on Ad Manager][gam]
|
|
51
|
-
*
|
|
52
|
-
* Type: _Predefined_
|
|
53
|
-
*
|
|
54
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=259767
|
|
55
|
-
*/
|
|
56
|
-
br: typeof brands[keyof typeof brands] | null;
|
|
57
|
-
/**
|
|
58
|
-
* **Co**ntributor - [see on Ad Manager][gam]
|
|
59
|
-
*
|
|
60
|
-
* Array of all contributors to the content on the page
|
|
61
|
-
*
|
|
62
|
-
* Type: _Dynamic_
|
|
63
|
-
*
|
|
64
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186207
|
|
65
|
-
*/
|
|
66
|
-
co: string[];
|
|
67
|
-
/**
|
|
68
|
-
* **C**ontent **T**ype - [see on Ad Manager][gam]
|
|
69
|
-
*
|
|
70
|
-
* Type: _Predefined_
|
|
71
|
-
*
|
|
72
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177807
|
|
73
|
-
*/
|
|
74
|
-
ct: typeof contentTypes[number];
|
|
75
16
|
/**
|
|
76
17
|
* **D**ot**c**om-**r**endering **E**ligible - [see on Ad Manager][gam]
|
|
77
18
|
*
|
|
@@ -80,34 +21,6 @@ export declare type ContentTargeting = {
|
|
|
80
21
|
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11958028
|
|
81
22
|
*/
|
|
82
23
|
dcre: True | False;
|
|
83
|
-
/**
|
|
84
|
-
* **Edition** - [see on Ad Manager][gam]
|
|
85
|
-
*
|
|
86
|
-
* Type: _Predefined_
|
|
87
|
-
*
|
|
88
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174207
|
|
89
|
-
*/
|
|
90
|
-
edition: typeof editions[keyof typeof editions];
|
|
91
|
-
/**
|
|
92
|
-
* **K**eywords - [see on Ad Manager][gam]
|
|
93
|
-
*
|
|
94
|
-
* Type: _Dynamic_
|
|
95
|
-
*
|
|
96
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177687
|
|
97
|
-
*/
|
|
98
|
-
k: string[];
|
|
99
|
-
/**
|
|
100
|
-
* **Ob**server Content - [see on Ad Manager][gam]
|
|
101
|
-
*
|
|
102
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=256887
|
|
103
|
-
*/
|
|
104
|
-
ob: 't' | null;
|
|
105
|
-
/**
|
|
106
|
-
* **P**latform - [see on Ad Manager][gam]
|
|
107
|
-
*
|
|
108
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180207
|
|
109
|
-
*/
|
|
110
|
-
p: typeof platforms[keyof typeof platforms];
|
|
111
24
|
/**
|
|
112
25
|
* Rendering Platform - [see on Ad Manager][gam]
|
|
113
26
|
*
|
|
@@ -120,42 +33,12 @@ export declare type ContentTargeting = {
|
|
|
120
33
|
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=173967
|
|
121
34
|
*/
|
|
122
35
|
s: string;
|
|
123
|
-
/**
|
|
124
|
-
* **Se**ries - [see on Ad Manager][gam]
|
|
125
|
-
*
|
|
126
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180447
|
|
127
|
-
*/
|
|
128
|
-
se: string[];
|
|
129
36
|
/**
|
|
130
37
|
* **Sens**itive - [see on Ad Manager][gam]
|
|
131
38
|
*
|
|
132
39
|
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11654206
|
|
133
40
|
*/
|
|
134
41
|
sens: True | False;
|
|
135
|
-
/**
|
|
136
|
-
* **Su**rging Article - [see on Ad Manager][gam]
|
|
137
|
-
*
|
|
138
|
-
* Type: _Predefined_
|
|
139
|
-
*
|
|
140
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=185007
|
|
141
|
-
*/
|
|
142
|
-
su: Array<typeof surges[keyof typeof surges]>;
|
|
143
|
-
/**
|
|
144
|
-
* **T**o**n**es - [see on Ad Manager][gam]
|
|
145
|
-
*
|
|
146
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=191487
|
|
147
|
-
*/
|
|
148
|
-
tn: string[];
|
|
149
|
-
/**
|
|
150
|
-
* **U**niform **R**esource **L**ocator - [see on Ad Manager][gam]
|
|
151
|
-
*
|
|
152
|
-
* Relative to `www.theguardian.com`, starts with `/`
|
|
153
|
-
*
|
|
154
|
-
* Type: _Dynamic_
|
|
155
|
-
*
|
|
156
|
-
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174327
|
|
157
|
-
*/
|
|
158
|
-
url: `/${string}`;
|
|
159
42
|
/**
|
|
160
43
|
* URL Keywords - [see on Ad Manager][gam]
|
|
161
44
|
*
|
|
@@ -175,15 +58,12 @@ export declare type ContentTargeting = {
|
|
|
175
58
|
*/
|
|
176
59
|
vl: null | typeof videoLengths[number];
|
|
177
60
|
};
|
|
178
|
-
export declare const getContentTargeting: ({
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
61
|
+
export declare const getContentTargeting: ({ eligibleForDCR, path, renderingPlatform, section, sensitive, videoLength, }: {
|
|
62
|
+
eligibleForDCR: boolean;
|
|
63
|
+
path: SharedTargeting['url'];
|
|
64
|
+
renderingPlatform: ContentTargeting['rp'];
|
|
65
|
+
section: ContentTargeting['s'];
|
|
183
66
|
sensitive: boolean;
|
|
184
|
-
tones: ContentTargeting['tn'];
|
|
185
|
-
path: ContentTargeting['url'];
|
|
186
67
|
videoLength?: number | undefined;
|
|
187
|
-
|
|
188
|
-
}, targeting: Omit<ContentTargeting, 'br' | 'ct' | 'co' | 'p' | 'sens' | 'tn' | 'url' | 'urlkw' | 'vl' | 'su'>) => ContentTargeting;
|
|
68
|
+
}) => ContentTargeting;
|
|
189
69
|
export {};
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
import { isString } from '@guardian/libs';
|
|
2
2
|
/* -- Types -- */
|
|
3
|
-
const brands = {
|
|
4
|
-
Foundation: 'f',
|
|
5
|
-
Paid: 'p',
|
|
6
|
-
Sponsored: 's',
|
|
7
|
-
};
|
|
8
|
-
const editions = {
|
|
9
|
-
UnitedKingdom: 'uk',
|
|
10
|
-
UnitedStates: 'us',
|
|
11
|
-
Australia: 'au',
|
|
12
|
-
International: 'int',
|
|
13
|
-
};
|
|
14
3
|
const videoLengths = [
|
|
15
4
|
'25',
|
|
16
5
|
'30',
|
|
@@ -24,32 +13,6 @@ const videoLengths = [
|
|
|
24
13
|
'270',
|
|
25
14
|
'300',
|
|
26
15
|
];
|
|
27
|
-
const surges = {
|
|
28
|
-
0: '0',
|
|
29
|
-
50: '5',
|
|
30
|
-
100: '4',
|
|
31
|
-
200: '3',
|
|
32
|
-
300: '2',
|
|
33
|
-
400: '1',
|
|
34
|
-
};
|
|
35
|
-
const platforms = {
|
|
36
|
-
R2: 'r2',
|
|
37
|
-
NextGen: 'ng',
|
|
38
|
-
MobileApp: 'app',
|
|
39
|
-
AcceleratedMobilePages: 'amp',
|
|
40
|
-
};
|
|
41
|
-
const contentTypes = [
|
|
42
|
-
'article',
|
|
43
|
-
'audio',
|
|
44
|
-
'crossword',
|
|
45
|
-
'gallery',
|
|
46
|
-
'interactive',
|
|
47
|
-
'liveblog',
|
|
48
|
-
'network-front',
|
|
49
|
-
'section',
|
|
50
|
-
'tag',
|
|
51
|
-
'video',
|
|
52
|
-
];
|
|
53
16
|
/* -- Methods -- */
|
|
54
17
|
const getVideoLength = (videoLength) => {
|
|
55
18
|
const index = Math.min(Math.ceil(videoLength / 30), 10);
|
|
@@ -62,24 +25,13 @@ const getUrlKeywords = (url) => {
|
|
|
62
25
|
.slice(-1)[0];
|
|
63
26
|
return isString(lastSegment) ? lastSegment.split('-').filter(Boolean) : [];
|
|
64
27
|
};
|
|
65
|
-
const getSurgingParam = (surging) => {
|
|
66
|
-
if (surging < 50 || isNaN(surging))
|
|
67
|
-
return ['0'];
|
|
68
|
-
const thresholds = [400, 300, 200, 100, 50];
|
|
69
|
-
return thresholds.filter((n) => n <= surging).map((s) => surges[s]);
|
|
70
|
-
};
|
|
71
28
|
/* -- Targeting -- */
|
|
72
|
-
export const getContentTargeting = ({
|
|
29
|
+
export const getContentTargeting = ({ eligibleForDCR, path, renderingPlatform, section, sensitive, videoLength, }) => {
|
|
73
30
|
return {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
ct: contentType,
|
|
78
|
-
p: platforms[platform],
|
|
31
|
+
dcre: eligibleForDCR ? 't' : 'f',
|
|
32
|
+
rp: renderingPlatform,
|
|
33
|
+
s: section,
|
|
79
34
|
sens: sensitive ? 't' : 'f',
|
|
80
|
-
tn: tones,
|
|
81
|
-
su: getSurgingParam(surging),
|
|
82
|
-
url: path,
|
|
83
35
|
urlkw: getUrlKeywords(path),
|
|
84
36
|
vl: videoLength ? getVideoLength(videoLength) : null,
|
|
85
37
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConditionalExcept } from 'type-fest';
|
|
2
|
+
declare type ValidTargetingObject<Base> = ConditionalExcept<Base, null | undefined | '' | readonly [] | readonly [''] | boolean | number>;
|
|
3
|
+
/**
|
|
4
|
+
* Picks only keys with targeting values from an object.
|
|
5
|
+
* A targeting values is defined as either:
|
|
6
|
+
* - a non-empty string
|
|
7
|
+
* - an array of non-empty strings
|
|
8
|
+
*
|
|
9
|
+
* If you object is read-only, you can safely access properties on the result.
|
|
10
|
+
* For example:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* dirty = {
|
|
14
|
+
* valid: 'real',
|
|
15
|
+
* invalid: undefined,
|
|
16
|
+
* } as const;
|
|
17
|
+
*
|
|
18
|
+
* clean = pickDefinedValues(dirty);
|
|
19
|
+
*
|
|
20
|
+
* // @ts-expect-error -- you can’t access this property
|
|
21
|
+
* clean.invalid
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const pickTargetingValues: <T extends Record<string, string | readonly string[] | undefined>>(obj: T) => import("type-fest").Except<T, NonNullable<{ [Key in keyof T]: T[Key] extends number | boolean | "" | readonly [] | readonly [""] | null | undefined ? Key : never; }[keyof T]>>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isString } from '@guardian/libs';
|
|
2
|
+
const isTargetingString = (string) => isString(string) && string !== '';
|
|
3
|
+
const isTargetingArray = (array) => Array.isArray(array) && array.filter(isTargetingString).length > 0;
|
|
4
|
+
const isValidTargeting = (value) => {
|
|
5
|
+
if (isTargetingString(value))
|
|
6
|
+
return true;
|
|
7
|
+
if (isTargetingArray(value))
|
|
8
|
+
return true;
|
|
9
|
+
return false;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Picks only keys with targeting values from an object.
|
|
13
|
+
* A targeting values is defined as either:
|
|
14
|
+
* - a non-empty string
|
|
15
|
+
* - an array of non-empty strings
|
|
16
|
+
*
|
|
17
|
+
* If you object is read-only, you can safely access properties on the result.
|
|
18
|
+
* For example:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* dirty = {
|
|
22
|
+
* valid: 'real',
|
|
23
|
+
* invalid: undefined,
|
|
24
|
+
* } as const;
|
|
25
|
+
*
|
|
26
|
+
* clean = pickDefinedValues(dirty);
|
|
27
|
+
*
|
|
28
|
+
* // @ts-expect-error -- you can’t access this property
|
|
29
|
+
* clean.invalid
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const pickTargetingValues = (obj) => {
|
|
33
|
+
const initialValue = {};
|
|
34
|
+
return Object.entries(obj).reduce((valid, [key, value]) => {
|
|
35
|
+
if (isValidTargeting(value)) {
|
|
36
|
+
// @ts-expect-error -- isValidTargeting checks this
|
|
37
|
+
valid[key] = Array.isArray(value)
|
|
38
|
+
? value.filter(isTargetingString)
|
|
39
|
+
: value;
|
|
40
|
+
}
|
|
41
|
+
return valid;
|
|
42
|
+
}, initialValue);
|
|
43
|
+
};
|
|
@@ -41,8 +41,9 @@ const experimentsTargeting = ({ clientSideParticipations, serverSideParticipatio
|
|
|
41
41
|
const serverSideExperiments = Object.entries(serverSideParticipations)
|
|
42
42
|
.map((test) => testToParams(...test))
|
|
43
43
|
.filter(isString);
|
|
44
|
-
if (clientSideExperiment.length + serverSideExperiments.length === 0)
|
|
44
|
+
if (clientSideExperiment.length + serverSideExperiments.length === 0) {
|
|
45
45
|
return null;
|
|
46
|
+
}
|
|
46
47
|
return [...clientSideExperiment, ...serverSideExperiments];
|
|
47
48
|
};
|
|
48
49
|
/* -- Targeting -- */
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
declare const brands: {
|
|
2
|
+
readonly Foundation: "f";
|
|
3
|
+
readonly Paid: "p";
|
|
4
|
+
readonly Sponsored: "s";
|
|
5
|
+
};
|
|
6
|
+
declare const contentTypes: readonly ["article", "audio", "crossword", "gallery", "interactive", "liveblog", "network-front", "section", "tag", "video"];
|
|
7
|
+
declare const editions: {
|
|
8
|
+
readonly UnitedKingdom: "uk";
|
|
9
|
+
readonly UnitedStates: "us";
|
|
10
|
+
readonly Australia: "au";
|
|
11
|
+
readonly International: "int";
|
|
12
|
+
};
|
|
13
|
+
declare const platforms: {
|
|
14
|
+
readonly R2: "r2";
|
|
15
|
+
readonly NextGen: "ng";
|
|
16
|
+
readonly MobileApp: "app";
|
|
17
|
+
readonly AcceleratedMobilePages: "amp";
|
|
18
|
+
};
|
|
19
|
+
declare const surges: {
|
|
20
|
+
readonly 0: "0";
|
|
21
|
+
readonly 50: "5";
|
|
22
|
+
readonly 100: "4";
|
|
23
|
+
readonly 200: "3";
|
|
24
|
+
readonly 300: "2";
|
|
25
|
+
readonly 400: "1";
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Shared Targeting is passed by `frontend` https://git.io/JDJ6W
|
|
29
|
+
*
|
|
30
|
+
* It is generated in `commercial-shared` https://git.io/JDJ62
|
|
31
|
+
*
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
export declare type SharedTargeting = {
|
|
36
|
+
/**
|
|
37
|
+
* **Bl**og tags – [see on Ad Manager][gam]
|
|
38
|
+
*
|
|
39
|
+
* Type: _Dynamic_
|
|
40
|
+
*
|
|
41
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186687
|
|
42
|
+
*/
|
|
43
|
+
bl: string[];
|
|
44
|
+
/**
|
|
45
|
+
* **Br**anding - [see on Ad Manager][gam]
|
|
46
|
+
*
|
|
47
|
+
* Type: _Predefined_
|
|
48
|
+
*
|
|
49
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=259767
|
|
50
|
+
*/
|
|
51
|
+
br: typeof brands[keyof typeof brands];
|
|
52
|
+
/**
|
|
53
|
+
* **Co**ntributors and Authors - [see on Ad Manager][gam]
|
|
54
|
+
*
|
|
55
|
+
* Array of all contributors to the content on the page
|
|
56
|
+
*
|
|
57
|
+
* Type: _Dynamic_
|
|
58
|
+
*
|
|
59
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186207
|
|
60
|
+
*/
|
|
61
|
+
co: string[];
|
|
62
|
+
/**
|
|
63
|
+
* **C**ontent **T**ype - [see on Ad Manager][gam]
|
|
64
|
+
*
|
|
65
|
+
* Type: _Predefined_
|
|
66
|
+
*
|
|
67
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177807
|
|
68
|
+
*/
|
|
69
|
+
ct: typeof contentTypes[number];
|
|
70
|
+
/**
|
|
71
|
+
* **Edition** - [see on Ad Manager][gam]
|
|
72
|
+
*
|
|
73
|
+
* Type: _Predefined_
|
|
74
|
+
*
|
|
75
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174207
|
|
76
|
+
*/
|
|
77
|
+
edition: typeof editions[keyof typeof editions];
|
|
78
|
+
/**
|
|
79
|
+
* **K**eywords - [see on Ad Manager][gam]
|
|
80
|
+
*
|
|
81
|
+
* Type: _Dynamic_
|
|
82
|
+
*
|
|
83
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177687
|
|
84
|
+
*/
|
|
85
|
+
k: string[];
|
|
86
|
+
/**
|
|
87
|
+
* **Ob**server Content - [see on Ad Manager][gam]
|
|
88
|
+
*
|
|
89
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=256887
|
|
90
|
+
*/
|
|
91
|
+
ob: 't';
|
|
92
|
+
/**
|
|
93
|
+
* **P**latform - [see on Ad Manager][gam]
|
|
94
|
+
*
|
|
95
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180207
|
|
96
|
+
*/
|
|
97
|
+
p: typeof platforms[keyof typeof platforms];
|
|
98
|
+
/**
|
|
99
|
+
* **Se**ries - [see on Ad Manager][gam]
|
|
100
|
+
*
|
|
101
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=180447
|
|
102
|
+
*/
|
|
103
|
+
se: string[];
|
|
104
|
+
/**
|
|
105
|
+
* **Sh**ort URL - [see on Ad Manager][gam]
|
|
106
|
+
*
|
|
107
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=286047
|
|
108
|
+
*/
|
|
109
|
+
sh: `https://www.theguardian.com/p/${string}`;
|
|
110
|
+
/**
|
|
111
|
+
* **Su**rging Article - [see on Ad Manager][gam]
|
|
112
|
+
*
|
|
113
|
+
* Type: _Predefined_
|
|
114
|
+
*
|
|
115
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=185007
|
|
116
|
+
*/
|
|
117
|
+
su: Array<typeof surges[keyof typeof surges]>;
|
|
118
|
+
/**
|
|
119
|
+
* **T**o**n**es - [see on Ad Manager][gam]
|
|
120
|
+
*
|
|
121
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=191487
|
|
122
|
+
*/
|
|
123
|
+
tn: string[];
|
|
124
|
+
/**
|
|
125
|
+
* **U**niform **R**esource **L**ocator - [see on Ad Manager][gam]
|
|
126
|
+
*
|
|
127
|
+
* Relative to `www.theguardian.com`, starts with `/`
|
|
128
|
+
*
|
|
129
|
+
* Type: _Dynamic_
|
|
130
|
+
*
|
|
131
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=174327
|
|
132
|
+
*/
|
|
133
|
+
url: `/${string}`;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* What goes in comes out
|
|
137
|
+
*/
|
|
138
|
+
export declare const getSharedTargeting: (shared: Partial<SharedTargeting>) => Partial<SharedTargeting>;
|
|
139
|
+
export declare const _: {
|
|
140
|
+
getSurgingParam: (surging: number) => SharedTargeting['su'];
|
|
141
|
+
};
|
|
142
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { pickTargetingValues } from './pick-targeting-values';
|
|
2
|
+
/* -- Types -- */
|
|
3
|
+
const brands = {
|
|
4
|
+
Foundation: 'f',
|
|
5
|
+
Paid: 'p',
|
|
6
|
+
Sponsored: 's',
|
|
7
|
+
};
|
|
8
|
+
const contentTypes = [
|
|
9
|
+
'article',
|
|
10
|
+
'audio',
|
|
11
|
+
'crossword',
|
|
12
|
+
'gallery',
|
|
13
|
+
'interactive',
|
|
14
|
+
'liveblog',
|
|
15
|
+
'network-front',
|
|
16
|
+
'section',
|
|
17
|
+
'tag',
|
|
18
|
+
'video',
|
|
19
|
+
];
|
|
20
|
+
const editions = {
|
|
21
|
+
UnitedKingdom: 'uk',
|
|
22
|
+
UnitedStates: 'us',
|
|
23
|
+
Australia: 'au',
|
|
24
|
+
International: 'int',
|
|
25
|
+
};
|
|
26
|
+
const platforms = {
|
|
27
|
+
R2: 'r2',
|
|
28
|
+
NextGen: 'ng',
|
|
29
|
+
MobileApp: 'app',
|
|
30
|
+
AcceleratedMobilePages: 'amp',
|
|
31
|
+
};
|
|
32
|
+
const surges = {
|
|
33
|
+
0: '0',
|
|
34
|
+
50: '5',
|
|
35
|
+
100: '4',
|
|
36
|
+
200: '3',
|
|
37
|
+
300: '2',
|
|
38
|
+
400: '1',
|
|
39
|
+
};
|
|
40
|
+
/* -- Methods -- */
|
|
41
|
+
const getSurgingParam = (surging) => {
|
|
42
|
+
if (surging < 50 || isNaN(surging))
|
|
43
|
+
return ['0'];
|
|
44
|
+
const thresholds = [400, 300, 200, 100, 50];
|
|
45
|
+
return thresholds.filter((n) => n <= surging).map((s) => surges[s]);
|
|
46
|
+
};
|
|
47
|
+
/* -- Targeting -- */
|
|
48
|
+
/**
|
|
49
|
+
* What goes in comes out
|
|
50
|
+
*/
|
|
51
|
+
export const getSharedTargeting = (shared) => pickTargetingValues(shared);
|
|
52
|
+
export const _ = {
|
|
53
|
+
getSurgingParam,
|
|
54
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guardian/commercial-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.1",
|
|
4
4
|
"description": "Guardian advertising business logic",
|
|
5
5
|
"homepage": "https://github.com/guardian/commercial-core#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@typescript-eslint/parser": "^5.5.0",
|
|
58
58
|
"commitizen": "^4.2.4",
|
|
59
59
|
"cz-conventional-changelog": "^3.3.0",
|
|
60
|
-
"eslint": "^8.
|
|
60
|
+
"eslint": "^8.4.1",
|
|
61
61
|
"eslint-config-prettier": "^8.3.0",
|
|
62
62
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
63
63
|
"eslint-plugin-import": "^2.25.3",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"prettier": "^2.5.0",
|
|
72
72
|
"semantic-release": "^18.0.1",
|
|
73
73
|
"ts-jest": "^27.0.7",
|
|
74
|
+
"type-fest": "^2.8.0",
|
|
74
75
|
"typescript": "^4.5.2",
|
|
75
76
|
"web-vitals": "^2.1.2"
|
|
76
77
|
},
|