@guardian/commercial-core 0.28.0 → 0.32.0
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 +4 -4
- package/dist/cjs/EventTimer.js +24 -21
- package/dist/cjs/ad-targeting-youtube.js +2 -2
- package/dist/cjs/targeting/personalised.d.ts +78 -0
- package/dist/cjs/targeting/personalised.js +148 -0
- package/dist/cjs/targeting/session.d.ts +98 -0
- package/dist/cjs/targeting/session.js +57 -0
- package/dist/cjs/types.d.ts +5 -5
- package/dist/esm/EventTimer.d.ts +4 -4
- package/dist/esm/EventTimer.js +24 -21
- package/dist/esm/ad-targeting-youtube.js +1 -1
- package/dist/esm/targeting/personalised.d.ts +78 -0
- package/dist/esm/targeting/personalised.js +145 -0
- package/dist/esm/targeting/session.d.ts +98 -0
- package/dist/esm/targeting/session.js +53 -0
- package/dist/esm/types.d.ts +5 -5
- package/package.json +23 -21
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
|
@@ -37,10 +37,10 @@ export declare class EventTimer {
|
|
|
37
37
|
effectiveType?: string;
|
|
38
38
|
};
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
40
|
+
* Initialise the EventTimer class on page.
|
|
41
41
|
* Returns the singleton instance of the EventTimer class and binds
|
|
42
42
|
* to window.guardian.commercialTimer. If it's been previously
|
|
43
|
-
*
|
|
43
|
+
* initialised and bound it returns the original instance
|
|
44
44
|
* Note: We save to window.guardian.commercialTimer because
|
|
45
45
|
* different bundles (DCR / DCP) can use commercial core, and we want
|
|
46
46
|
* all timer events saved to a single instance per-page
|
|
@@ -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
|
@@ -66,10 +66,10 @@ class EventTimer {
|
|
|
66
66
|
: {};
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Initialise the EventTimer class on page.
|
|
70
70
|
* Returns the singleton instance of the EventTimer class and binds
|
|
71
71
|
* to window.guardian.commercialTimer. If it's been previously
|
|
72
|
-
*
|
|
72
|
+
* initialised and bound it returns the original instance
|
|
73
73
|
* Note: We save to window.guardian.commercialTimer because
|
|
74
74
|
* different bundles (DCR / DCP) can use commercial core, and we want
|
|
75
75
|
* all timer events saved to a single instance per-page
|
|
@@ -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'
|
|
@@ -125,7 +111,7 @@ class EventTimer {
|
|
|
125
111
|
* @param {origin} [origin=page] - Either 'page' (default) or the name of the slot
|
|
126
112
|
*/
|
|
127
113
|
trigger(eventName, origin = 'page') {
|
|
128
|
-
const
|
|
114
|
+
const TRACKED_SLOT_NAME = 'top-above-nav';
|
|
129
115
|
if (origin === 'page' &&
|
|
130
116
|
!this.triggers.page[eventName]) {
|
|
131
117
|
this.mark(eventName);
|
|
@@ -139,12 +125,26 @@ class EventTimer {
|
|
|
139
125
|
this.trackInGA(eventName, trackLabel);
|
|
140
126
|
this.triggers.first[eventName] = true;
|
|
141
127
|
}
|
|
142
|
-
if (origin ===
|
|
143
|
-
if (!this.triggers[
|
|
144
|
-
const trackLabel = `${
|
|
128
|
+
if (origin === TRACKED_SLOT_NAME) {
|
|
129
|
+
if (!this.triggers[TRACKED_SLOT_NAME][eventName]) {
|
|
130
|
+
const trackLabel = `${TRACKED_SLOT_NAME}-${eventName}`;
|
|
145
131
|
this.mark(trackLabel);
|
|
146
132
|
this.trackInGA(eventName, trackLabel);
|
|
147
|
-
this.triggers[
|
|
133
|
+
this.triggers[TRACKED_SLOT_NAME][eventName] = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
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
148
|
}
|
|
149
149
|
}
|
|
150
150
|
}
|
|
@@ -159,9 +159,12 @@ class EventTimer {
|
|
|
159
159
|
exports.EventTimer = EventTimer;
|
|
160
160
|
EventTimer._externallyDefinedEventNames = [
|
|
161
161
|
'cmp-tcfv2-init',
|
|
162
|
+
'cmp-tcfv2-ui-displayed',
|
|
162
163
|
'cmp-tcfv2-got-consent',
|
|
163
164
|
'cmp-ccpa-init',
|
|
165
|
+
'cmp-ccpa-ui-displayed',
|
|
164
166
|
'cmp-ccpa-got-consent',
|
|
165
167
|
'cmp-aus-init',
|
|
168
|
+
'cmp-aus-ui-displayed',
|
|
166
169
|
'cmp-aus-got-consent',
|
|
167
170
|
];
|
|
@@ -5,6 +5,8 @@ const libs_1 = require("@guardian/libs");
|
|
|
5
5
|
const can_use_dom_1 = require("./lib/can-use-dom");
|
|
6
6
|
const construct_query_1 = require("./lib/construct-query");
|
|
7
7
|
const permutive_1 = require("./permutive");
|
|
8
|
+
const disabledAds = { disableAds: true };
|
|
9
|
+
exports.disabledAds = disabledAds;
|
|
8
10
|
const buildCustomParamsFromCookies = () => (0, can_use_dom_1.canUseDom)()
|
|
9
11
|
? {
|
|
10
12
|
permutive: (0, permutive_1.getPermutivePFPSegments)(),
|
|
@@ -54,8 +56,6 @@ const buildAdsConfig = (cmpConsent, adUnit, customParams) => {
|
|
|
54
56
|
// Shouldn't happen but handle if no matching framework
|
|
55
57
|
return disabledAds;
|
|
56
58
|
};
|
|
57
|
-
const disabledAds = { disableAds: true };
|
|
58
|
-
exports.disabledAds = disabledAds;
|
|
59
59
|
const buildAdsConfigWithConsent = (isAdFreeUser, adUnit, customParamsToMerge, consentState) => {
|
|
60
60
|
if (isAdFreeUser) {
|
|
61
61
|
return disabledAds;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ConsentState } from '@guardian/consent-management-platform/dist/types';
|
|
2
|
+
import type { TCEventStatusCode } from '@guardian/consent-management-platform/dist/types/tcfv2';
|
|
3
|
+
import type { False, NotApplicable, True } from '../types';
|
|
4
|
+
declare const frequency: readonly ["0", "1", "2", "3", "4", "5", "6-9", "10-15", "16-19", "20-29", "30plus"];
|
|
5
|
+
declare const adManagerGroups: readonly ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
|
|
6
|
+
declare 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
|
+
export declare 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: typeof frequency[number];
|
|
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
|
+
declare const getPersonalisedTargeting: (state: ConsentState) => PersonalisedTargeting;
|
|
78
|
+
export { getPersonalisedTargeting };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPersonalisedTargeting = void 0;
|
|
4
|
+
const libs_1 = require("@guardian/libs");
|
|
5
|
+
const permutive_1 = require("../permutive");
|
|
6
|
+
/* -- Types -- */
|
|
7
|
+
const frequency = [
|
|
8
|
+
'0',
|
|
9
|
+
'1',
|
|
10
|
+
'2',
|
|
11
|
+
'3',
|
|
12
|
+
'4',
|
|
13
|
+
'5',
|
|
14
|
+
'6-9',
|
|
15
|
+
'10-15',
|
|
16
|
+
'16-19',
|
|
17
|
+
'20-29',
|
|
18
|
+
'30plus',
|
|
19
|
+
];
|
|
20
|
+
const AMTGRP_STORAGE_KEY = 'gu.adManagerGroup';
|
|
21
|
+
const adManagerGroups = [
|
|
22
|
+
'1',
|
|
23
|
+
'2',
|
|
24
|
+
'3',
|
|
25
|
+
'4',
|
|
26
|
+
'5',
|
|
27
|
+
'6',
|
|
28
|
+
'7',
|
|
29
|
+
'8',
|
|
30
|
+
'9',
|
|
31
|
+
'10',
|
|
32
|
+
'11',
|
|
33
|
+
'12',
|
|
34
|
+
];
|
|
35
|
+
/* -- Methods -- */
|
|
36
|
+
const getRawWithConsent = (key, state) => {
|
|
37
|
+
if (state.tcfv2) {
|
|
38
|
+
if (state.tcfv2.consents['1'])
|
|
39
|
+
return libs_1.storage.local.getRaw(key);
|
|
40
|
+
}
|
|
41
|
+
if (state.ccpa) {
|
|
42
|
+
if (!state.ccpa.doNotSell)
|
|
43
|
+
return libs_1.storage.local.getRaw(key);
|
|
44
|
+
}
|
|
45
|
+
if (state.aus) {
|
|
46
|
+
if (state.aus.personalisedAdvertising)
|
|
47
|
+
return libs_1.storage.local.getRaw(key);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
const getFrequencyValue = (state) => {
|
|
52
|
+
const rawValue = getRawWithConsent('gu.alreadyVisited', state);
|
|
53
|
+
if (!rawValue)
|
|
54
|
+
return '0';
|
|
55
|
+
const visitCount = parseInt(rawValue, 10);
|
|
56
|
+
if (visitCount <= 5) {
|
|
57
|
+
return frequency[visitCount] ?? '0';
|
|
58
|
+
}
|
|
59
|
+
else if (visitCount >= 6 && visitCount <= 9) {
|
|
60
|
+
return '6-9';
|
|
61
|
+
}
|
|
62
|
+
else if (visitCount >= 10 && visitCount <= 15) {
|
|
63
|
+
return '10-15';
|
|
64
|
+
}
|
|
65
|
+
else if (visitCount >= 16 && visitCount <= 19) {
|
|
66
|
+
return '16-19';
|
|
67
|
+
}
|
|
68
|
+
else if (visitCount >= 20 && visitCount <= 29) {
|
|
69
|
+
return '20-29';
|
|
70
|
+
}
|
|
71
|
+
else if (visitCount >= 30) {
|
|
72
|
+
return '30plus';
|
|
73
|
+
}
|
|
74
|
+
return '0';
|
|
75
|
+
};
|
|
76
|
+
const tcfv2AllPurposesConsented = (consents) => Object.keys(consents).length > 0 && Object.values(consents).every(Boolean);
|
|
77
|
+
const personalisedAdvertising = (state) => {
|
|
78
|
+
if (state.tcfv2)
|
|
79
|
+
return tcfv2AllPurposesConsented(state.tcfv2.consents);
|
|
80
|
+
if (state.ccpa)
|
|
81
|
+
return !state.ccpa.doNotSell;
|
|
82
|
+
if (state.aus)
|
|
83
|
+
return state.aus.personalisedAdvertising;
|
|
84
|
+
return false;
|
|
85
|
+
};
|
|
86
|
+
const getCMPTargeting = (state) => {
|
|
87
|
+
if (state.tcfv2) {
|
|
88
|
+
return {
|
|
89
|
+
cmp_interaction: state.tcfv2.eventStatus,
|
|
90
|
+
pa: tcfv2AllPurposesConsented(state.tcfv2.consents) ? 't' : 'f',
|
|
91
|
+
consent_tcfv2: tcfv2AllPurposesConsented(state.tcfv2.consents)
|
|
92
|
+
? 't'
|
|
93
|
+
: 'f',
|
|
94
|
+
rdp: 'na',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (state.ccpa) {
|
|
98
|
+
return {
|
|
99
|
+
consent_tcfv2: 'na',
|
|
100
|
+
rdp: state.ccpa.doNotSell ? 't' : 'f',
|
|
101
|
+
pa: state.ccpa.doNotSell ? 'f' : 't',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (state.aus) {
|
|
105
|
+
return {
|
|
106
|
+
consent_tcfv2: 'na',
|
|
107
|
+
rdp: 'na',
|
|
108
|
+
pa: state.aus.personalisedAdvertising ? 't' : 'f',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
cmp_interaction: 'na',
|
|
113
|
+
consent_tcfv2: 'na',
|
|
114
|
+
rdp: 'na',
|
|
115
|
+
pa: 'f',
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const isAdManagerGroup = (s) => adManagerGroups.some((g) => g === s);
|
|
119
|
+
const createAdManagerGroup = () => {
|
|
120
|
+
const index = Math.floor(Math.random() * adManagerGroups.length);
|
|
121
|
+
const group = adManagerGroups[index] ?? '12';
|
|
122
|
+
libs_1.storage.local.setRaw(AMTGRP_STORAGE_KEY, group);
|
|
123
|
+
return group;
|
|
124
|
+
};
|
|
125
|
+
const getAdManagerGroup = (state) => {
|
|
126
|
+
if (!personalisedAdvertising(state)) {
|
|
127
|
+
libs_1.storage.local.remove(AMTGRP_STORAGE_KEY);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const existingGroup = libs_1.storage.local.getRaw(AMTGRP_STORAGE_KEY);
|
|
131
|
+
return isAdManagerGroup(existingGroup)
|
|
132
|
+
? existingGroup
|
|
133
|
+
: createAdManagerGroup();
|
|
134
|
+
};
|
|
135
|
+
const getPermutiveWithState = (state) => {
|
|
136
|
+
if (personalisedAdvertising(state))
|
|
137
|
+
return (0, permutive_1.getPermutiveSegments)();
|
|
138
|
+
(0, permutive_1.clearPermutiveSegments)();
|
|
139
|
+
return [];
|
|
140
|
+
};
|
|
141
|
+
/* -- Targeting -- */
|
|
142
|
+
const getPersonalisedTargeting = (state) => ({
|
|
143
|
+
amtgrp: getAdManagerGroup(state),
|
|
144
|
+
fr: getFrequencyValue(state),
|
|
145
|
+
permutive: getPermutiveWithState(state),
|
|
146
|
+
...getCMPTargeting(state),
|
|
147
|
+
});
|
|
148
|
+
exports.getPersonalisedTargeting = getPersonalisedTargeting;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Participations } from '@guardian/ab-core';
|
|
2
|
+
import type { CountryCode } from '@guardian/libs';
|
|
3
|
+
import type { False, True } from '../types';
|
|
4
|
+
declare const referrers: readonly [{
|
|
5
|
+
readonly id: "facebook";
|
|
6
|
+
readonly match: "facebook.com";
|
|
7
|
+
}, {
|
|
8
|
+
readonly id: "google";
|
|
9
|
+
readonly match: "www.google";
|
|
10
|
+
}, {
|
|
11
|
+
readonly id: "twitter";
|
|
12
|
+
readonly match: "/t.co/";
|
|
13
|
+
}, {
|
|
14
|
+
readonly id: "reddit";
|
|
15
|
+
readonly match: "reddit.com";
|
|
16
|
+
}];
|
|
17
|
+
/**
|
|
18
|
+
* Session Targeting is based on the browser session
|
|
19
|
+
*
|
|
20
|
+
* Includes information such as the country of origin, referrer, page view ID.
|
|
21
|
+
*
|
|
22
|
+
* These values identify a browser session are either generated client-side,
|
|
23
|
+
* read from a cookie or passed down from the server.
|
|
24
|
+
*/
|
|
25
|
+
export declare type SessionTargeting = {
|
|
26
|
+
/**
|
|
27
|
+
* **AB** Tests – [see on Ad Manager][gam]
|
|
28
|
+
*
|
|
29
|
+
* Type: _Dynamic_
|
|
30
|
+
*
|
|
31
|
+
* Values: typically start with `ab`
|
|
32
|
+
*
|
|
33
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186327
|
|
34
|
+
*/
|
|
35
|
+
ab: string[] | null;
|
|
36
|
+
/**
|
|
37
|
+
* **A**d **T**est – [see on Ad Manager][gam]
|
|
38
|
+
*
|
|
39
|
+
* Used for testing purposes, based on query param and/or cookie.
|
|
40
|
+
*
|
|
41
|
+
* Type: _Dynamic_
|
|
42
|
+
*
|
|
43
|
+
* [See Current values](https://frontend.gutools.co.uk/commercial/adtests)
|
|
44
|
+
*
|
|
45
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177567
|
|
46
|
+
*/
|
|
47
|
+
at: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* **C**ountry **C**ode – [see on Ad Manager][gam]
|
|
50
|
+
*
|
|
51
|
+
* Type: _Dynamic_
|
|
52
|
+
*
|
|
53
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11703293
|
|
54
|
+
*/
|
|
55
|
+
cc: CountryCode;
|
|
56
|
+
/**
|
|
57
|
+
* Ophan **P**age **V**iew id – [see on Ad Manager][gam]
|
|
58
|
+
*
|
|
59
|
+
* ID Generated client-side, usually available on
|
|
60
|
+
* `guardian.config.ophan.pageViewId`
|
|
61
|
+
*
|
|
62
|
+
* Used mainly for internal reporting
|
|
63
|
+
*
|
|
64
|
+
* Type: _Dynamic_
|
|
65
|
+
*
|
|
66
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=206127
|
|
67
|
+
*/
|
|
68
|
+
pv: string;
|
|
69
|
+
/**
|
|
70
|
+
* **Ref**errer – [see on Ad Manager][gam]
|
|
71
|
+
*
|
|
72
|
+
* Type: _Dynamic_
|
|
73
|
+
*
|
|
74
|
+
* Sample values:
|
|
75
|
+
* - `facebook`
|
|
76
|
+
* - `google`
|
|
77
|
+
* - `googleplus`
|
|
78
|
+
* - `reddit`
|
|
79
|
+
* - `twitter`
|
|
80
|
+
*
|
|
81
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=228567
|
|
82
|
+
*/
|
|
83
|
+
ref: typeof referrers[number]['id'] | null;
|
|
84
|
+
/**
|
|
85
|
+
* **S**igned **I**n – [see on Ad Manager][gam]
|
|
86
|
+
*
|
|
87
|
+
*Whether a user is signed in. Based on presence of a cookie.
|
|
88
|
+
*
|
|
89
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=215727
|
|
90
|
+
*/
|
|
91
|
+
si: True | False;
|
|
92
|
+
};
|
|
93
|
+
export declare type AllParticipations = {
|
|
94
|
+
clientSideParticipations: Participations;
|
|
95
|
+
serverSideParticipations: Record<string, 'control' | 'variant'>;
|
|
96
|
+
};
|
|
97
|
+
export declare const getSessionTargeting: (referrer: string, participations: AllParticipations, targeting: Omit<SessionTargeting, 'ab' | 'ref'>) => SessionTargeting;
|
|
98
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSessionTargeting = void 0;
|
|
4
|
+
const libs_1 = require("@guardian/libs");
|
|
5
|
+
/* -- Types -- */
|
|
6
|
+
const referrers = [
|
|
7
|
+
{
|
|
8
|
+
id: 'facebook',
|
|
9
|
+
match: 'facebook.com',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'google',
|
|
13
|
+
match: 'www.google',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'twitter',
|
|
17
|
+
match: '/t.co/',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'reddit',
|
|
21
|
+
match: 'reddit.com',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
/* -- Methods -- */
|
|
25
|
+
const getReferrer = (referrer) => {
|
|
26
|
+
if (referrer === '')
|
|
27
|
+
return null;
|
|
28
|
+
const matchedRef = referrers.find((referrerType) => referrer.includes(referrerType.match)) ?? null;
|
|
29
|
+
return matchedRef ? matchedRef.id : null;
|
|
30
|
+
};
|
|
31
|
+
const experimentsTargeting = ({ clientSideParticipations, serverSideParticipations, }) => {
|
|
32
|
+
const testToParams = (testName, variant) => {
|
|
33
|
+
if (variant === 'notintest')
|
|
34
|
+
return null;
|
|
35
|
+
// GAM key-value pairs accept value strings up to 40 characters long
|
|
36
|
+
return `${testName}-${variant}`.substring(0, 40);
|
|
37
|
+
};
|
|
38
|
+
const clientSideExperiment = Object.entries(clientSideParticipations)
|
|
39
|
+
.map((test) => {
|
|
40
|
+
const [name, variant] = test;
|
|
41
|
+
return testToParams(name, variant.variant);
|
|
42
|
+
})
|
|
43
|
+
.filter(libs_1.isString);
|
|
44
|
+
const serverSideExperiments = Object.entries(serverSideParticipations)
|
|
45
|
+
.map((test) => testToParams(...test))
|
|
46
|
+
.filter(libs_1.isString);
|
|
47
|
+
if (clientSideExperiment.length + serverSideExperiments.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
return [...clientSideExperiment, ...serverSideExperiments];
|
|
50
|
+
};
|
|
51
|
+
/* -- Targeting -- */
|
|
52
|
+
const getSessionTargeting = (referrer, participations, targeting) => ({
|
|
53
|
+
ref: getReferrer(referrer),
|
|
54
|
+
ab: experimentsTargeting(participations),
|
|
55
|
+
...targeting,
|
|
56
|
+
});
|
|
57
|
+
exports.getSessionTargeting = getSessionTargeting;
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
export declare type
|
|
1
|
+
export declare type TagAttribute = {
|
|
2
2
|
name: string;
|
|
3
3
|
value: string;
|
|
4
4
|
};
|
|
5
|
-
export declare type GetThirdPartyTag = (arg0: {
|
|
6
|
-
shouldRun: boolean;
|
|
7
|
-
}) => ThirdPartyTag;
|
|
8
5
|
export declare type ThirdPartyTag = {
|
|
9
6
|
async?: boolean;
|
|
10
|
-
attrs?:
|
|
7
|
+
attrs?: TagAttribute[];
|
|
11
8
|
beforeLoad?: () => void;
|
|
12
9
|
insertSnippet?: () => void;
|
|
13
10
|
loaded?: boolean;
|
|
@@ -17,6 +14,9 @@ export declare type ThirdPartyTag = {
|
|
|
17
14
|
url?: string;
|
|
18
15
|
useImage?: boolean;
|
|
19
16
|
};
|
|
17
|
+
export declare type GetThirdPartyTag = (arg0: {
|
|
18
|
+
shouldRun: boolean;
|
|
19
|
+
}) => ThirdPartyTag;
|
|
20
20
|
export declare type GuardianAnalyticsConfig = {
|
|
21
21
|
trackers: Record<string, string>;
|
|
22
22
|
};
|
package/dist/esm/EventTimer.d.ts
CHANGED
|
@@ -37,10 +37,10 @@ export declare class EventTimer {
|
|
|
37
37
|
effectiveType?: string;
|
|
38
38
|
};
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
40
|
+
* Initialise the EventTimer class on page.
|
|
41
41
|
* Returns the singleton instance of the EventTimer class and binds
|
|
42
42
|
* to window.guardian.commercialTimer. If it's been previously
|
|
43
|
-
*
|
|
43
|
+
* initialised and bound it returns the original instance
|
|
44
44
|
* Note: We save to window.guardian.commercialTimer because
|
|
45
45
|
* different bundles (DCR / DCP) can use commercial core, and we want
|
|
46
46
|
* all timer events saved to a single instance per-page
|
|
@@ -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
|
@@ -63,10 +63,10 @@ export class EventTimer {
|
|
|
63
63
|
: {};
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Initialise the EventTimer class on page.
|
|
67
67
|
* Returns the singleton instance of the EventTimer class and binds
|
|
68
68
|
* to window.guardian.commercialTimer. If it's been previously
|
|
69
|
-
*
|
|
69
|
+
* initialised and bound it returns the original instance
|
|
70
70
|
* Note: We save to window.guardian.commercialTimer because
|
|
71
71
|
* different bundles (DCR / DCP) can use commercial core, and we want
|
|
72
72
|
* all timer events saved to a single instance per-page
|
|
@@ -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'
|
|
@@ -122,7 +108,7 @@ export class EventTimer {
|
|
|
122
108
|
* @param {origin} [origin=page] - Either 'page' (default) or the name of the slot
|
|
123
109
|
*/
|
|
124
110
|
trigger(eventName, origin = 'page') {
|
|
125
|
-
const
|
|
111
|
+
const TRACKED_SLOT_NAME = 'top-above-nav';
|
|
126
112
|
if (origin === 'page' &&
|
|
127
113
|
!this.triggers.page[eventName]) {
|
|
128
114
|
this.mark(eventName);
|
|
@@ -136,12 +122,26 @@ export class EventTimer {
|
|
|
136
122
|
this.trackInGA(eventName, trackLabel);
|
|
137
123
|
this.triggers.first[eventName] = true;
|
|
138
124
|
}
|
|
139
|
-
if (origin ===
|
|
140
|
-
if (!this.triggers[
|
|
141
|
-
const trackLabel = `${
|
|
125
|
+
if (origin === TRACKED_SLOT_NAME) {
|
|
126
|
+
if (!this.triggers[TRACKED_SLOT_NAME][eventName]) {
|
|
127
|
+
const trackLabel = `${TRACKED_SLOT_NAME}-${eventName}`;
|
|
142
128
|
this.mark(trackLabel);
|
|
143
129
|
this.trackInGA(eventName, trackLabel);
|
|
144
|
-
this.triggers[
|
|
130
|
+
this.triggers[TRACKED_SLOT_NAME][eventName] = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
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
145
|
}
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -155,9 +155,12 @@ export class EventTimer {
|
|
|
155
155
|
}
|
|
156
156
|
EventTimer._externallyDefinedEventNames = [
|
|
157
157
|
'cmp-tcfv2-init',
|
|
158
|
+
'cmp-tcfv2-ui-displayed',
|
|
158
159
|
'cmp-tcfv2-got-consent',
|
|
159
160
|
'cmp-ccpa-init',
|
|
161
|
+
'cmp-ccpa-ui-displayed',
|
|
160
162
|
'cmp-ccpa-got-consent',
|
|
161
163
|
'cmp-aus-init',
|
|
164
|
+
'cmp-aus-ui-displayed',
|
|
162
165
|
'cmp-aus-got-consent',
|
|
163
166
|
];
|
|
@@ -2,6 +2,7 @@ import { getCookie } from '@guardian/libs';
|
|
|
2
2
|
import { canUseDom } from './lib/can-use-dom';
|
|
3
3
|
import { constructQuery } from './lib/construct-query';
|
|
4
4
|
import { getPermutivePFPSegments } from './permutive';
|
|
5
|
+
const disabledAds = { disableAds: true };
|
|
5
6
|
const buildCustomParamsFromCookies = () => canUseDom()
|
|
6
7
|
? {
|
|
7
8
|
permutive: getPermutivePFPSegments(),
|
|
@@ -51,7 +52,6 @@ const buildAdsConfig = (cmpConsent, adUnit, customParams) => {
|
|
|
51
52
|
// Shouldn't happen but handle if no matching framework
|
|
52
53
|
return disabledAds;
|
|
53
54
|
};
|
|
54
|
-
const disabledAds = { disableAds: true };
|
|
55
55
|
const buildAdsConfigWithConsent = (isAdFreeUser, adUnit, customParamsToMerge, consentState) => {
|
|
56
56
|
if (isAdFreeUser) {
|
|
57
57
|
return disabledAds;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ConsentState } from '@guardian/consent-management-platform/dist/types';
|
|
2
|
+
import type { TCEventStatusCode } from '@guardian/consent-management-platform/dist/types/tcfv2';
|
|
3
|
+
import type { False, NotApplicable, True } from '../types';
|
|
4
|
+
declare const frequency: readonly ["0", "1", "2", "3", "4", "5", "6-9", "10-15", "16-19", "20-29", "30plus"];
|
|
5
|
+
declare const adManagerGroups: readonly ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
|
|
6
|
+
declare 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
|
+
export declare 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: typeof frequency[number];
|
|
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
|
+
declare const getPersonalisedTargeting: (state: ConsentState) => PersonalisedTargeting;
|
|
78
|
+
export { getPersonalisedTargeting };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { storage } from '@guardian/libs';
|
|
2
|
+
import { clearPermutiveSegments, getPermutiveSegments } from '../permutive';
|
|
3
|
+
/* -- Types -- */
|
|
4
|
+
const frequency = [
|
|
5
|
+
'0',
|
|
6
|
+
'1',
|
|
7
|
+
'2',
|
|
8
|
+
'3',
|
|
9
|
+
'4',
|
|
10
|
+
'5',
|
|
11
|
+
'6-9',
|
|
12
|
+
'10-15',
|
|
13
|
+
'16-19',
|
|
14
|
+
'20-29',
|
|
15
|
+
'30plus',
|
|
16
|
+
];
|
|
17
|
+
const AMTGRP_STORAGE_KEY = 'gu.adManagerGroup';
|
|
18
|
+
const adManagerGroups = [
|
|
19
|
+
'1',
|
|
20
|
+
'2',
|
|
21
|
+
'3',
|
|
22
|
+
'4',
|
|
23
|
+
'5',
|
|
24
|
+
'6',
|
|
25
|
+
'7',
|
|
26
|
+
'8',
|
|
27
|
+
'9',
|
|
28
|
+
'10',
|
|
29
|
+
'11',
|
|
30
|
+
'12',
|
|
31
|
+
];
|
|
32
|
+
/* -- Methods -- */
|
|
33
|
+
const getRawWithConsent = (key, state) => {
|
|
34
|
+
if (state.tcfv2) {
|
|
35
|
+
if (state.tcfv2.consents['1'])
|
|
36
|
+
return storage.local.getRaw(key);
|
|
37
|
+
}
|
|
38
|
+
if (state.ccpa) {
|
|
39
|
+
if (!state.ccpa.doNotSell)
|
|
40
|
+
return storage.local.getRaw(key);
|
|
41
|
+
}
|
|
42
|
+
if (state.aus) {
|
|
43
|
+
if (state.aus.personalisedAdvertising)
|
|
44
|
+
return storage.local.getRaw(key);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
const getFrequencyValue = (state) => {
|
|
49
|
+
const rawValue = getRawWithConsent('gu.alreadyVisited', state);
|
|
50
|
+
if (!rawValue)
|
|
51
|
+
return '0';
|
|
52
|
+
const visitCount = parseInt(rawValue, 10);
|
|
53
|
+
if (visitCount <= 5) {
|
|
54
|
+
return frequency[visitCount] ?? '0';
|
|
55
|
+
}
|
|
56
|
+
else if (visitCount >= 6 && visitCount <= 9) {
|
|
57
|
+
return '6-9';
|
|
58
|
+
}
|
|
59
|
+
else if (visitCount >= 10 && visitCount <= 15) {
|
|
60
|
+
return '10-15';
|
|
61
|
+
}
|
|
62
|
+
else if (visitCount >= 16 && visitCount <= 19) {
|
|
63
|
+
return '16-19';
|
|
64
|
+
}
|
|
65
|
+
else if (visitCount >= 20 && visitCount <= 29) {
|
|
66
|
+
return '20-29';
|
|
67
|
+
}
|
|
68
|
+
else if (visitCount >= 30) {
|
|
69
|
+
return '30plus';
|
|
70
|
+
}
|
|
71
|
+
return '0';
|
|
72
|
+
};
|
|
73
|
+
const tcfv2AllPurposesConsented = (consents) => Object.keys(consents).length > 0 && Object.values(consents).every(Boolean);
|
|
74
|
+
const personalisedAdvertising = (state) => {
|
|
75
|
+
if (state.tcfv2)
|
|
76
|
+
return tcfv2AllPurposesConsented(state.tcfv2.consents);
|
|
77
|
+
if (state.ccpa)
|
|
78
|
+
return !state.ccpa.doNotSell;
|
|
79
|
+
if (state.aus)
|
|
80
|
+
return state.aus.personalisedAdvertising;
|
|
81
|
+
return false;
|
|
82
|
+
};
|
|
83
|
+
const getCMPTargeting = (state) => {
|
|
84
|
+
if (state.tcfv2) {
|
|
85
|
+
return {
|
|
86
|
+
cmp_interaction: state.tcfv2.eventStatus,
|
|
87
|
+
pa: tcfv2AllPurposesConsented(state.tcfv2.consents) ? 't' : 'f',
|
|
88
|
+
consent_tcfv2: tcfv2AllPurposesConsented(state.tcfv2.consents)
|
|
89
|
+
? 't'
|
|
90
|
+
: 'f',
|
|
91
|
+
rdp: 'na',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (state.ccpa) {
|
|
95
|
+
return {
|
|
96
|
+
consent_tcfv2: 'na',
|
|
97
|
+
rdp: state.ccpa.doNotSell ? 't' : 'f',
|
|
98
|
+
pa: state.ccpa.doNotSell ? 'f' : 't',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (state.aus) {
|
|
102
|
+
return {
|
|
103
|
+
consent_tcfv2: 'na',
|
|
104
|
+
rdp: 'na',
|
|
105
|
+
pa: state.aus.personalisedAdvertising ? 't' : 'f',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
cmp_interaction: 'na',
|
|
110
|
+
consent_tcfv2: 'na',
|
|
111
|
+
rdp: 'na',
|
|
112
|
+
pa: 'f',
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
const isAdManagerGroup = (s) => adManagerGroups.some((g) => g === s);
|
|
116
|
+
const createAdManagerGroup = () => {
|
|
117
|
+
const index = Math.floor(Math.random() * adManagerGroups.length);
|
|
118
|
+
const group = adManagerGroups[index] ?? '12';
|
|
119
|
+
storage.local.setRaw(AMTGRP_STORAGE_KEY, group);
|
|
120
|
+
return group;
|
|
121
|
+
};
|
|
122
|
+
const getAdManagerGroup = (state) => {
|
|
123
|
+
if (!personalisedAdvertising(state)) {
|
|
124
|
+
storage.local.remove(AMTGRP_STORAGE_KEY);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const existingGroup = storage.local.getRaw(AMTGRP_STORAGE_KEY);
|
|
128
|
+
return isAdManagerGroup(existingGroup)
|
|
129
|
+
? existingGroup
|
|
130
|
+
: createAdManagerGroup();
|
|
131
|
+
};
|
|
132
|
+
const getPermutiveWithState = (state) => {
|
|
133
|
+
if (personalisedAdvertising(state))
|
|
134
|
+
return getPermutiveSegments();
|
|
135
|
+
clearPermutiveSegments();
|
|
136
|
+
return [];
|
|
137
|
+
};
|
|
138
|
+
/* -- Targeting -- */
|
|
139
|
+
const getPersonalisedTargeting = (state) => ({
|
|
140
|
+
amtgrp: getAdManagerGroup(state),
|
|
141
|
+
fr: getFrequencyValue(state),
|
|
142
|
+
permutive: getPermutiveWithState(state),
|
|
143
|
+
...getCMPTargeting(state),
|
|
144
|
+
});
|
|
145
|
+
export { getPersonalisedTargeting };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Participations } from '@guardian/ab-core';
|
|
2
|
+
import type { CountryCode } from '@guardian/libs';
|
|
3
|
+
import type { False, True } from '../types';
|
|
4
|
+
declare const referrers: readonly [{
|
|
5
|
+
readonly id: "facebook";
|
|
6
|
+
readonly match: "facebook.com";
|
|
7
|
+
}, {
|
|
8
|
+
readonly id: "google";
|
|
9
|
+
readonly match: "www.google";
|
|
10
|
+
}, {
|
|
11
|
+
readonly id: "twitter";
|
|
12
|
+
readonly match: "/t.co/";
|
|
13
|
+
}, {
|
|
14
|
+
readonly id: "reddit";
|
|
15
|
+
readonly match: "reddit.com";
|
|
16
|
+
}];
|
|
17
|
+
/**
|
|
18
|
+
* Session Targeting is based on the browser session
|
|
19
|
+
*
|
|
20
|
+
* Includes information such as the country of origin, referrer, page view ID.
|
|
21
|
+
*
|
|
22
|
+
* These values identify a browser session are either generated client-side,
|
|
23
|
+
* read from a cookie or passed down from the server.
|
|
24
|
+
*/
|
|
25
|
+
export declare type SessionTargeting = {
|
|
26
|
+
/**
|
|
27
|
+
* **AB** Tests – [see on Ad Manager][gam]
|
|
28
|
+
*
|
|
29
|
+
* Type: _Dynamic_
|
|
30
|
+
*
|
|
31
|
+
* Values: typically start with `ab`
|
|
32
|
+
*
|
|
33
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=186327
|
|
34
|
+
*/
|
|
35
|
+
ab: string[] | null;
|
|
36
|
+
/**
|
|
37
|
+
* **A**d **T**est – [see on Ad Manager][gam]
|
|
38
|
+
*
|
|
39
|
+
* Used for testing purposes, based on query param and/or cookie.
|
|
40
|
+
*
|
|
41
|
+
* Type: _Dynamic_
|
|
42
|
+
*
|
|
43
|
+
* [See Current values](https://frontend.gutools.co.uk/commercial/adtests)
|
|
44
|
+
*
|
|
45
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=177567
|
|
46
|
+
*/
|
|
47
|
+
at: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* **C**ountry **C**ode – [see on Ad Manager][gam]
|
|
50
|
+
*
|
|
51
|
+
* Type: _Dynamic_
|
|
52
|
+
*
|
|
53
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11703293
|
|
54
|
+
*/
|
|
55
|
+
cc: CountryCode;
|
|
56
|
+
/**
|
|
57
|
+
* Ophan **P**age **V**iew id – [see on Ad Manager][gam]
|
|
58
|
+
*
|
|
59
|
+
* ID Generated client-side, usually available on
|
|
60
|
+
* `guardian.config.ophan.pageViewId`
|
|
61
|
+
*
|
|
62
|
+
* Used mainly for internal reporting
|
|
63
|
+
*
|
|
64
|
+
* Type: _Dynamic_
|
|
65
|
+
*
|
|
66
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=206127
|
|
67
|
+
*/
|
|
68
|
+
pv: string;
|
|
69
|
+
/**
|
|
70
|
+
* **Ref**errer – [see on Ad Manager][gam]
|
|
71
|
+
*
|
|
72
|
+
* Type: _Dynamic_
|
|
73
|
+
*
|
|
74
|
+
* Sample values:
|
|
75
|
+
* - `facebook`
|
|
76
|
+
* - `google`
|
|
77
|
+
* - `googleplus`
|
|
78
|
+
* - `reddit`
|
|
79
|
+
* - `twitter`
|
|
80
|
+
*
|
|
81
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=228567
|
|
82
|
+
*/
|
|
83
|
+
ref: typeof referrers[number]['id'] | null;
|
|
84
|
+
/**
|
|
85
|
+
* **S**igned **I**n – [see on Ad Manager][gam]
|
|
86
|
+
*
|
|
87
|
+
*Whether a user is signed in. Based on presence of a cookie.
|
|
88
|
+
*
|
|
89
|
+
* [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=215727
|
|
90
|
+
*/
|
|
91
|
+
si: True | False;
|
|
92
|
+
};
|
|
93
|
+
export declare type AllParticipations = {
|
|
94
|
+
clientSideParticipations: Participations;
|
|
95
|
+
serverSideParticipations: Record<string, 'control' | 'variant'>;
|
|
96
|
+
};
|
|
97
|
+
export declare const getSessionTargeting: (referrer: string, participations: AllParticipations, targeting: Omit<SessionTargeting, 'ab' | 'ref'>) => SessionTargeting;
|
|
98
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isString } from '@guardian/libs';
|
|
2
|
+
/* -- Types -- */
|
|
3
|
+
const referrers = [
|
|
4
|
+
{
|
|
5
|
+
id: 'facebook',
|
|
6
|
+
match: 'facebook.com',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: 'google',
|
|
10
|
+
match: 'www.google',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'twitter',
|
|
14
|
+
match: '/t.co/',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'reddit',
|
|
18
|
+
match: 'reddit.com',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
/* -- Methods -- */
|
|
22
|
+
const getReferrer = (referrer) => {
|
|
23
|
+
if (referrer === '')
|
|
24
|
+
return null;
|
|
25
|
+
const matchedRef = referrers.find((referrerType) => referrer.includes(referrerType.match)) ?? null;
|
|
26
|
+
return matchedRef ? matchedRef.id : null;
|
|
27
|
+
};
|
|
28
|
+
const experimentsTargeting = ({ clientSideParticipations, serverSideParticipations, }) => {
|
|
29
|
+
const testToParams = (testName, variant) => {
|
|
30
|
+
if (variant === 'notintest')
|
|
31
|
+
return null;
|
|
32
|
+
// GAM key-value pairs accept value strings up to 40 characters long
|
|
33
|
+
return `${testName}-${variant}`.substring(0, 40);
|
|
34
|
+
};
|
|
35
|
+
const clientSideExperiment = Object.entries(clientSideParticipations)
|
|
36
|
+
.map((test) => {
|
|
37
|
+
const [name, variant] = test;
|
|
38
|
+
return testToParams(name, variant.variant);
|
|
39
|
+
})
|
|
40
|
+
.filter(isString);
|
|
41
|
+
const serverSideExperiments = Object.entries(serverSideParticipations)
|
|
42
|
+
.map((test) => testToParams(...test))
|
|
43
|
+
.filter(isString);
|
|
44
|
+
if (clientSideExperiment.length + serverSideExperiments.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
return [...clientSideExperiment, ...serverSideExperiments];
|
|
47
|
+
};
|
|
48
|
+
/* -- Targeting -- */
|
|
49
|
+
export const getSessionTargeting = (referrer, participations, targeting) => ({
|
|
50
|
+
ref: getReferrer(referrer),
|
|
51
|
+
ab: experimentsTargeting(participations),
|
|
52
|
+
...targeting,
|
|
53
|
+
});
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
export declare type
|
|
1
|
+
export declare type TagAttribute = {
|
|
2
2
|
name: string;
|
|
3
3
|
value: string;
|
|
4
4
|
};
|
|
5
|
-
export declare type GetThirdPartyTag = (arg0: {
|
|
6
|
-
shouldRun: boolean;
|
|
7
|
-
}) => ThirdPartyTag;
|
|
8
5
|
export declare type ThirdPartyTag = {
|
|
9
6
|
async?: boolean;
|
|
10
|
-
attrs?:
|
|
7
|
+
attrs?: TagAttribute[];
|
|
11
8
|
beforeLoad?: () => void;
|
|
12
9
|
insertSnippet?: () => void;
|
|
13
10
|
loaded?: boolean;
|
|
@@ -17,6 +14,9 @@ export declare type ThirdPartyTag = {
|
|
|
17
14
|
url?: string;
|
|
18
15
|
useImage?: boolean;
|
|
19
16
|
};
|
|
17
|
+
export declare type GetThirdPartyTag = (arg0: {
|
|
18
|
+
shouldRun: boolean;
|
|
19
|
+
}) => ThirdPartyTag;
|
|
20
20
|
export declare type GuardianAnalyticsConfig = {
|
|
21
21
|
trackers: Record<string, string>;
|
|
22
22
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guardian/commercial-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Guardian advertising business logic",
|
|
5
5
|
"homepage": "https://github.com/guardian/commercial-core#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -41,42 +41,44 @@
|
|
|
41
41
|
},
|
|
42
42
|
"prettier": "@guardian/prettier",
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@commitlint/cli": "^
|
|
45
|
-
"@commitlint/config-conventional": "^
|
|
46
|
-
"@guardian/
|
|
47
|
-
"@guardian/
|
|
48
|
-
"@guardian/
|
|
49
|
-
"@guardian/
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
44
|
+
"@commitlint/cli": "^15.0.0",
|
|
45
|
+
"@commitlint/config-conventional": "^15.0.0",
|
|
46
|
+
"@guardian/ab-core": "^2.0.0",
|
|
47
|
+
"@guardian/consent-management-platform": "^8.0.1",
|
|
48
|
+
"@guardian/eslint-config-typescript": "^0.7.0",
|
|
49
|
+
"@guardian/libs": "3.5.1",
|
|
50
|
+
"@guardian/prettier": "^0.7.0",
|
|
51
|
+
"@octokit/core": "^3.5.1",
|
|
52
|
+
"@semantic-release/github": "^8.0.2",
|
|
53
53
|
"@types/google.analytics": "^0.0.42",
|
|
54
|
-
"@types/
|
|
55
|
-
"@
|
|
56
|
-
"@typescript-eslint/
|
|
54
|
+
"@types/googletag": "^1.1.4",
|
|
55
|
+
"@types/jest": "^27.0.3",
|
|
56
|
+
"@typescript-eslint/eslint-plugin": "^5.5.0",
|
|
57
|
+
"@typescript-eslint/parser": "^5.5.0",
|
|
57
58
|
"commitizen": "^4.2.4",
|
|
58
59
|
"cz-conventional-changelog": "^3.3.0",
|
|
59
|
-
"eslint": "^
|
|
60
|
+
"eslint": "^8.3.0",
|
|
60
61
|
"eslint-config-prettier": "^8.3.0",
|
|
61
62
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
62
|
-
"eslint-plugin-import": "^2.25.
|
|
63
|
-
"eslint-plugin-jest": "^25.
|
|
63
|
+
"eslint-plugin-import": "^2.25.3",
|
|
64
|
+
"eslint-plugin-jest": "^25.3.0",
|
|
64
65
|
"eslint-plugin-prettier": "^4.0.0",
|
|
65
66
|
"husky": "^7.0.4",
|
|
66
|
-
"jest": "^27.
|
|
67
|
-
"lint-staged": "^
|
|
67
|
+
"jest": "^27.4.1",
|
|
68
|
+
"lint-staged": "^12.1.2",
|
|
68
69
|
"mockdate": "^3.0.5",
|
|
69
70
|
"npm-run-all": "^4.1.5",
|
|
70
|
-
"prettier": "^2.
|
|
71
|
-
"semantic-release": "^18.0.
|
|
71
|
+
"prettier": "^2.5.0",
|
|
72
|
+
"semantic-release": "^18.0.1",
|
|
72
73
|
"ts-jest": "^27.0.7",
|
|
73
|
-
"typescript": "^4.
|
|
74
|
+
"typescript": "^4.5.2",
|
|
74
75
|
"web-vitals": "^2.1.2"
|
|
75
76
|
},
|
|
76
77
|
"publishConfig": {
|
|
77
78
|
"access": "public"
|
|
78
79
|
},
|
|
79
80
|
"peerDependencies": {
|
|
81
|
+
"@guardian/ab-core": "^2.0.0",
|
|
80
82
|
"@guardian/libs": "^3.3.0"
|
|
81
83
|
}
|
|
82
84
|
}
|