Skip to content

Commit cc58030

Browse files
Create uniform consent details object for Ophan tracking (#2324)
* Revert "Sourcepoint Australia Migration (#2118)" This reverts commit a208b66. * Create function for uniform ophan consent tracking * Remove repeating afterEach
1 parent 452cf2a commit cc58030

File tree

10 files changed

+291
-1
lines changed

10 files changed

+291
-1
lines changed

.changeset/metal-doors-stand.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@guardian/libs': patch
3+
---
4+
5+
Adds a new helper function to produce a uniform consent payload for Ophan tracking across consent frameworks (TCFv2/USNAT/AUS),

apps/github-pages/src/components/CmpTest.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script>
22
// this maps to the version in libs/@guardian/libs
3-
import { cmp, onConsentChange, log, setCookie } from '@guardian/libs';
3+
import { cmp, onConsentChange, log } from '@guardian/libs';
44
import { onMount } from 'svelte';
55
66
let useNonAdvertisedList = window.location.search.includes('NON_ADV');
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { getConsentDetailsForOphan } from './getConsentDetailsForOphan';
2+
import type { ConsentState } from './types';
3+
import type { TCFv2ConsentState } from './types/tcfv2';
4+
5+
const tcfv2ConsentState: TCFv2ConsentState = {
6+
consents: { 1: true },
7+
eventStatus: 'tcloaded',
8+
vendorConsents: {
9+
['5efefe25b8e05c06542b2a77']: true,
10+
},
11+
addtlConsent: 'xyz',
12+
gdprApplies: true,
13+
tcString: 'YAAA',
14+
};
15+
16+
const consentStateForTCFV2: ConsentState = {
17+
tcfv2: tcfv2ConsentState,
18+
canTarget: true,
19+
framework: 'tcfv2',
20+
};
21+
22+
const nonConsentStateForUSNAT: ConsentState = {
23+
usnat: {
24+
doNotSell: true,
25+
signalStatus: 'ready',
26+
},
27+
canTarget: false,
28+
framework: 'usnat',
29+
};
30+
31+
const consentStateForUSNAT: ConsentState = {
32+
usnat: {
33+
doNotSell: false,
34+
signalStatus: 'ready',
35+
},
36+
canTarget: true,
37+
framework: 'usnat',
38+
};
39+
40+
const consentStateForAUS: ConsentState = {
41+
aus: {
42+
personalisedAdvertising: true,
43+
},
44+
canTarget: true,
45+
framework: 'aus',
46+
};
47+
48+
const nonConsentStateForAUS: ConsentState = {
49+
aus: {
50+
personalisedAdvertising: false,
51+
},
52+
canTarget: false,
53+
framework: 'aus',
54+
};
55+
56+
const failedConsentState: ConsentState = {
57+
canTarget: false,
58+
framework: null,
59+
};
60+
61+
function setCookie(name: string, value: string) {
62+
document.cookie = `${name}=${value}; path=/;`;
63+
}
64+
65+
function clearCookies(): void {
66+
document.cookie.split(';').forEach((cookie) => {
67+
document.cookie = cookie
68+
.replace(/^ +/, '')
69+
.replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`);
70+
});
71+
}
72+
73+
describe('getConsentDetailsForOphan', () => {
74+
afterEach(() => {
75+
clearCookies();
76+
jest.restoreAllMocks();
77+
});
78+
describe('when consent framework is TCFv2', () => {
79+
beforeEach(() => {
80+
setCookie('consentUUID', 'fakeConsentUUID');
81+
});
82+
83+
it('returns the correct consent details', () => {
84+
const ophanConsentDetails =
85+
getConsentDetailsForOphan(consentStateForTCFV2);
86+
expect(ophanConsentDetails).toEqual({
87+
consentJurisdiction: 'TCF',
88+
consentUUID: 'fakeConsentUUID',
89+
consent: 'YAAA',
90+
});
91+
});
92+
});
93+
94+
describe('when consent framework is USNAT', () => {
95+
describe('and the only cookie dropped is usnatUUID', () => {
96+
beforeEach(() => {
97+
setCookie('usnatUUID', 'fakeUsnatUUID');
98+
});
99+
it('returns the correct consent details - consent is true', () => {
100+
const ophanConsentDetails =
101+
getConsentDetailsForOphan(consentStateForUSNAT);
102+
expect(ophanConsentDetails).toEqual({
103+
consentJurisdiction: 'USNAT',
104+
consentUUID: 'fakeUsnatUUID',
105+
consent: 'true',
106+
});
107+
});
108+
109+
it('returns the correct consent details - consent is false', () => {
110+
const ophanConsentDetails = getConsentDetailsForOphan(
111+
nonConsentStateForUSNAT,
112+
);
113+
expect(ophanConsentDetails).toEqual({
114+
consentJurisdiction: 'USNAT',
115+
consentUUID: 'fakeUsnatUUID',
116+
consent: 'false',
117+
});
118+
});
119+
});
120+
121+
describe('and the only cookie dropped is ccpaUUID', () => {
122+
beforeEach(() => {
123+
setCookie('ccpaUUID', 'fakeCcpaUUID');
124+
});
125+
126+
it('returns the correct consent details - consent is true', () => {
127+
const ophanConsentDetails =
128+
getConsentDetailsForOphan(consentStateForUSNAT);
129+
expect(ophanConsentDetails).toEqual({
130+
consentJurisdiction: 'USNAT',
131+
consentUUID: 'fakeCcpaUUID',
132+
consent: 'true',
133+
});
134+
});
135+
136+
it('returns the correct consent details - consent is false', () => {
137+
const ophanConsentDetails = getConsentDetailsForOphan(
138+
nonConsentStateForUSNAT,
139+
);
140+
expect(ophanConsentDetails).toEqual({
141+
consentJurisdiction: 'USNAT',
142+
consentUUID: 'fakeCcpaUUID',
143+
consent: 'false',
144+
});
145+
});
146+
});
147+
148+
describe('and both ccpaUUID and usnatUUID are present', () => {
149+
beforeEach(() => {
150+
setCookie('ccpaUUID', 'fakeCcpaUUID');
151+
setCookie('usnatUUID', 'fakeUsnatUUID');
152+
});
153+
154+
it('returns the correct consent details - consent is true', () => {
155+
const ophanConsentDetails =
156+
getConsentDetailsForOphan(consentStateForUSNAT);
157+
expect(ophanConsentDetails).toEqual({
158+
consentJurisdiction: 'USNAT',
159+
consentUUID: 'fakeUsnatUUID',
160+
consent: 'true',
161+
});
162+
});
163+
164+
it('returns the correct consent details - consent is false', () => {
165+
const ophanConsentDetails = getConsentDetailsForOphan(
166+
nonConsentStateForUSNAT,
167+
);
168+
expect(ophanConsentDetails).toEqual({
169+
consentJurisdiction: 'USNAT',
170+
consentUUID: 'fakeUsnatUUID',
171+
consent: 'false',
172+
});
173+
});
174+
});
175+
});
176+
177+
describe('when consent framework is AUS', () => {
178+
beforeEach(() => {
179+
setCookie('ccpaUUID', 'fakeCcpaUUID');
180+
});
181+
182+
it('returns the correct consent details - consent is true', () => {
183+
const ophanConsentDetails = getConsentDetailsForOphan(consentStateForAUS);
184+
expect(ophanConsentDetails).toEqual({
185+
consentJurisdiction: 'AUS',
186+
consentUUID: 'fakeCcpaUUID',
187+
consent: 'true',
188+
});
189+
});
190+
191+
it('returns the correct consent details - consent is false', () => {
192+
const ophanConsentDetails = getConsentDetailsForOphan(
193+
nonConsentStateForAUS,
194+
);
195+
expect(ophanConsentDetails).toEqual({
196+
consentJurisdiction: 'AUS',
197+
consentUUID: 'fakeCcpaUUID',
198+
consent: 'false',
199+
});
200+
});
201+
});
202+
203+
describe('when consent framework is unknown', () => {
204+
it('returns the correct consent details', () => {
205+
const ophanConsentDetails = getConsentDetailsForOphan(failedConsentState);
206+
expect(ophanConsentDetails).toEqual({
207+
consentJurisdiction: 'OTHER',
208+
consentUUID: '',
209+
consent: '',
210+
});
211+
});
212+
});
213+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { getCookie } from '../cookies/getCookie';
2+
import type { ConsentState, OphanConsentDetails } from './types';
3+
4+
/**
5+
* This function generates the object to be sent to Ophan
6+
*
7+
* @param {ConsentState} consentState
8+
* @return {*} {OphanConsentDetails}
9+
*/
10+
export const getConsentDetailsForOphan = (
11+
consentState: ConsentState,
12+
): OphanConsentDetails => {
13+
if (consentState.tcfv2) {
14+
return {
15+
consentJurisdiction: 'TCF',
16+
consentUUID: getCookie({ name: 'consentUUID' }) ?? '',
17+
consent: consentState.tcfv2.tcString,
18+
};
19+
}
20+
21+
if (consentState.usnat) {
22+
// Users who interacted with the CCPA banner before the migration to usnat will still have a ccpaUUID cookie. The usnatUUID cookie is set when the USNAT banner is interacted with. We need to check both cookies to ensure we have the correct consentUUID.
23+
const consentUUID =
24+
getCookie({ name: 'usnatUUID' }) ?? getCookie({ name: 'ccpaUUID' });
25+
return {
26+
consentJurisdiction: 'USNAT',
27+
consentUUID: consentUUID ?? '',
28+
consent: consentState.usnat.doNotSell ? 'false' : 'true',
29+
};
30+
}
31+
32+
if (consentState.aus) {
33+
return {
34+
consentJurisdiction: 'AUS',
35+
consentUUID: getCookie({ name: 'ccpaUUID' }) ?? '',
36+
consent: consentState.aus.personalisedAdvertising ? 'true' : 'false',
37+
};
38+
}
39+
40+
return { consentJurisdiction: 'OTHER', consentUUID: '', consent: '' };
41+
};

libs/@guardian/libs/src/consent-management-platform/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { countries } from '../countries/countries';
44
import { log } from '../logger/logger';
55
import { CMP as UnifiedCMP } from './cmp';
66
import { disable, enable, isDisabled } from './disable';
7+
import { getConsentDetailsForOphan as clientGetConsentDetailsForOphan } from './getConsentDetailsForOphan';
78
import { getConsentFor as clientGetConsentFor } from './getConsentFor';
89
import { getFramework } from './getFramework';
910
import { onConsent as clientOnConsent } from './onConsent';
1011
import { onConsentChange as clientOnConsentChange } from './onConsentChange';
1112
import {
1213
isServerSide,
1314
cmp as serverCmp,
15+
getConsentDetailsForOphan as serverGetConsentDetailsForOphan,
1416
getConsentFor as serverGetConsentFor,
1517
onConsent as serverOnConsent,
1618
onConsentChange as serverOnConsentChange,
@@ -172,3 +174,8 @@ export const onConsentChange = isServerSide
172174
export const getConsentFor = isServerSide
173175
? serverGetConsentFor
174176
: (window.guCmpHotFix.getConsentFor ??= clientGetConsentFor);
177+
178+
export const getConsentDetailsForOphan = isServerSide
179+
? serverGetConsentDetailsForOphan
180+
: (window.guCmpHotFix.getConsentDetailsForOphan ??=
181+
clientGetConsentDetailsForOphan);

libs/@guardian/libs/src/consent-management-platform/server.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
ConsentState,
55
GetConsentFor,
66
OnConsentChange,
7+
OphanConsentDetails,
78
VendorName,
89
} from './types';
910

@@ -48,6 +49,18 @@ export const onConsentChange: OnConsentChange = () => {
4849
return serverSideWarn();
4950
};
5051

52+
export const getConsentDetailsForOphan = (
53+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- consentState is not used in the server-side implementation, but we want to keep the same function signature.
54+
consentState: ConsentState,
55+
): OphanConsentDetails => {
56+
serverSideWarn();
57+
return {
58+
consentJurisdiction: 'OTHER',
59+
consentUUID: '',
60+
consent: '',
61+
};
62+
};
63+
5164
export const getConsentFor: GetConsentFor = (
5265
vendor: VendorName,
5366
consent: ConsentState,

libs/@guardian/libs/src/consent-management-platform/types/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ export interface PubData {
4848
pageViewId?: string;
4949
[propName: string]: unknown;
5050
}
51+
52+
export interface OphanConsentDetails {
53+
consentJurisdiction: 'TCF' | 'USNAT' | 'AUS' | 'OTHER';
54+
consentUUID: string;
55+
consent: string;
56+
}
5157
export interface SourcepointImplementation {
5258
init: (
5359
framework: ConsentFramework,

libs/@guardian/libs/src/consent-management-platform/types/window.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { getConsentDetailsForOphan } from '../getConsentDetailsForOphan';
12
import type { getConsentFor } from '../getConsentFor';
23
import type { Property } from '../lib/property';
34
import type { Currency, EndPoint } from '../lib/sourcepointConfig';
@@ -20,6 +21,7 @@ type GuCmpHotFix = {
2021
onConsent?: typeof onConsent;
2122
onConsentChange?: typeof onConsentChange;
2223
getConsentFor?: typeof getConsentFor;
24+
getConsentDetailsForOphan?: typeof getConsentDetailsForOphan;
2325
};
2426

2527
declare global {

libs/@guardian/libs/src/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ describe('The package', () => {
77
'cmp',
88
'countries',
99
'debug',
10+
'getConsentDetailsForOphan',
1011
'getConsentFor',
1112
'getCookie',
1213
'getCountryByCountryCode',

libs/@guardian/libs/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { ArticleElementRole } from './ArticleElementRole/ArticleElementRole';
66

77
export {
88
cmp,
9+
getConsentDetailsForOphan,
910
getConsentFor,
1011
onConsent,
1112
onConsentChange,
@@ -16,6 +17,7 @@ export type {
1617
ConsentState,
1718
OnConsentChangeCallback,
1819
VendorName,
20+
OphanConsentDetails,
1921
} from './consent-management-platform/types';
2022
export type {
2123
TCEventStatusCode,

0 commit comments

Comments
 (0)