Skip to content

Commit fefed3c

Browse files
Merge ca39b2a into 452cf2a
2 parents 452cf2a + ca39b2a commit fefed3c

File tree

10 files changed

+294
-3
lines changed

10 files changed

+294
-3
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: 2 additions & 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, setCookie, getConsentDetailsForOphan } from '@guardian/libs';
44
import { onMount } from 'svelte';
55
66
let useNonAdvertisedList = window.location.search.includes('NON_ADV');
@@ -99,6 +99,7 @@
9999
onConsentChange((payload) => {
100100
logEvent({ title: 'onConsentChange', payload });
101101
consentState = payload;
102+
console.log('getConsentDetailsForOphan', getConsentDetailsForOphan(payload));
102103
});
103104
104105
onMount(async () => {
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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+
function setCookie(name: string, value: string) {
57+
document.cookie = `${name}=${value}; path=/;`;
58+
}
59+
60+
function clearCookies(): void {
61+
document.cookie.split(';').forEach((cookie) => {
62+
document.cookie = cookie
63+
.replace(/^ +/, '')
64+
.replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`);
65+
});
66+
}
67+
68+
describe('getConsentDetailsForOphan', () => {
69+
describe('when consent framework is TCFv2', () => {
70+
beforeEach(() => {
71+
setCookie('consentUUID', 'fakeConsentUUID');
72+
});
73+
74+
afterEach(() => {
75+
clearCookies();
76+
jest.restoreAllMocks();
77+
});
78+
it('returns the correct consent details', () => {
79+
const ophanConsentDetails =
80+
getConsentDetailsForOphan(consentStateForTCFV2);
81+
expect(ophanConsentDetails).toEqual({
82+
consentJurisdiction: 'TCF',
83+
consentUUID: 'fakeConsentUUID',
84+
consent: 'YAAA',
85+
});
86+
});
87+
});
88+
89+
describe('when consent framework is USNAT', () => {
90+
describe('and the only cookie dropped is usnatUUID', () => {
91+
beforeEach(() => {
92+
setCookie('usnatUUID', 'fakeUsnatUUID');
93+
});
94+
95+
afterEach(() => {
96+
clearCookies();
97+
jest.restoreAllMocks();
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+
afterEach(() => {
127+
clearCookies();
128+
jest.restoreAllMocks();
129+
});
130+
it('returns the correct consent details - consent is true', () => {
131+
const ophanConsentDetails =
132+
getConsentDetailsForOphan(consentStateForUSNAT);
133+
expect(ophanConsentDetails).toEqual({
134+
consentJurisdiction: 'USNAT',
135+
consentUUID: 'fakeCcpaUUID',
136+
consent: 'true',
137+
});
138+
});
139+
140+
it('returns the correct consent details - consent is false', () => {
141+
const ophanConsentDetails = getConsentDetailsForOphan(
142+
nonConsentStateForUSNAT,
143+
);
144+
expect(ophanConsentDetails).toEqual({
145+
consentJurisdiction: 'USNAT',
146+
consentUUID: 'fakeCcpaUUID',
147+
consent: 'false',
148+
});
149+
});
150+
});
151+
152+
describe('and both ccpaUUID and usnatUUID are present', () => {
153+
beforeEach(() => {
154+
setCookie('ccpaUUID', 'fakeCcpaUUID');
155+
setCookie('usnatUUID', 'fakeUsnatUUID');
156+
});
157+
158+
afterEach(() => {
159+
clearCookies();
160+
jest.restoreAllMocks();
161+
});
162+
it('returns the correct consent details - consent is true', () => {
163+
const ophanConsentDetails =
164+
getConsentDetailsForOphan(consentStateForUSNAT);
165+
expect(ophanConsentDetails).toEqual({
166+
consentJurisdiction: 'USNAT',
167+
consentUUID: 'fakeUsnatUUID',
168+
consent: 'true',
169+
});
170+
});
171+
172+
it('returns the correct consent details - consent is false', () => {
173+
const ophanConsentDetails = getConsentDetailsForOphan(
174+
nonConsentStateForUSNAT,
175+
);
176+
expect(ophanConsentDetails).toEqual({
177+
consentJurisdiction: 'USNAT',
178+
consentUUID: 'fakeUsnatUUID',
179+
consent: 'false',
180+
});
181+
});
182+
});
183+
});
184+
185+
describe('when consent framework is AUS', () => {
186+
beforeEach(() => {
187+
setCookie('ccpaUUID', 'fakeCcpaUUID');
188+
});
189+
190+
afterEach(() => {
191+
clearCookies();
192+
jest.restoreAllMocks();
193+
});
194+
it('returns the correct consent details - consent is true', () => {
195+
const ophanConsentDetails = getConsentDetailsForOphan(consentStateForAUS);
196+
expect(ophanConsentDetails).toEqual({
197+
consentJurisdiction: 'AUS',
198+
consentUUID: 'fakeCcpaUUID',
199+
consent: 'true',
200+
});
201+
});
202+
203+
it('returns the correct consent details - consent is false', () => {
204+
const ophanConsentDetails = getConsentDetailsForOphan(
205+
nonConsentStateForAUS,
206+
);
207+
expect(ophanConsentDetails).toEqual({
208+
consentJurisdiction: 'AUS',
209+
consentUUID: 'fakeCcpaUUID',
210+
consent: 'false',
211+
});
212+
});
213+
});
214+
});
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: 14 additions & 2 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

@@ -27,11 +28,10 @@ export const cmp: CMP = {
2728
__disable: serverSideWarn,
2829
__enable: serverSideWarnAndReturn(false),
2930
__isDisabled: serverSideWarnAndReturn(false),
30-
31+
version: '0.0.0-server',
3132
hasInitialised: serverSideWarnAndReturn(false),
3233
init: serverSideWarn,
3334
showPrivacyManager: serverSideWarn,
34-
version: 'n/a',
3535
willShowPrivacyMessage: serverSideWarnAndReturn(Promise.resolve(false)),
3636
willShowPrivacyMessageSync: serverSideWarnAndReturn(false),
3737
};
@@ -48,6 +48,18 @@ export const onConsentChange: OnConsentChange = () => {
4848
return serverSideWarn();
4949
};
5050

51+
export const getConsentDetailsForOphan = (
52+
// 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.
53+
consentState: ConsentState,
54+
): OphanConsentDetails => {
55+
serverSideWarn();
56+
return {
57+
consentJurisdiction: 'OTHER',
58+
consentUUID: '',
59+
consent: '',
60+
};
61+
};
62+
5163
export const getConsentFor: GetConsentFor = (
5264
vendor: VendorName,
5365
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)