Skip to content

Commit

Permalink
Merge 26aa123 into 3a93d78
Browse files Browse the repository at this point in the history
  • Loading branch information
akinsola-guardian authored Feb 10, 2025
2 parents 3a93d78 + 26aa123 commit 184a8a9
Show file tree
Hide file tree
Showing 16 changed files with 532 additions and 72 deletions.
5 changes: 5 additions & 0 deletions .changeset/breezy-drinks-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@guardian/libs': major
---

Add the ability for consent or pay by adding two new params to cmp.init
51 changes: 47 additions & 4 deletions apps/github-pages/src/components/CmpTest.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import { cmp, onConsentChange, log } from '@guardian/libs';
import { onMount } from 'svelte';
let useNonAdvertisedList = window.location.search.includes('CMP_MAIN');
let isUserSignedIn = window.location.search.includes('CMP_SIGNED_IN');
switch (window.location.hash) {
case '#tcfv2':
localStorage.setItem('framework', JSON.stringify('tcfv2'));
Expand Down Expand Up @@ -52,6 +55,29 @@
window.location.reload();
};
const toggleQueryParams = (param) => {
let queryParams = new URLSearchParams(window.location.search);
queryParams.has(param)
? queryParams.delete(param)
: queryParams.append(param, '');
window.location.search = queryParams.toString();
};
const toggleIsFeatureFlagEnabled = () => {
isFeatureFlagEnabled = !isFeatureFlagEnabled;
toggleQueryParams('CMP_COP');
};
const toggleIsUserSignedIn = () => {
isUserSignedIn = !isUserSignedIn;
toggleQueryParams('CMP_SIGNED_IN');
};
const toggleUseNonAdvertisedList = () => {
useNonAdvertisedList = !useNonAdvertisedList;
toggleQueryParams('CMP_MAIN');
};
let framework = JSON.parse(localStorage.getItem('framework'));
let setLocation = () => {
Expand Down Expand Up @@ -91,10 +117,11 @@
}
// do this loads to make sure that doesn't break things
cmp.init({ country });
cmp.init({ country });
cmp.init({ country });
cmp.init({ country });
cmp.init({
country,
isUserSignedIn: isUserSignedIn ?? false,
useNonAdvertisedList: useNonAdvertisedList ?? false,
});
});
</script>
Expand Down Expand Up @@ -133,6 +160,22 @@
in Australia:
<strong>CCPA-like</strong>
</label>
<label class={useNonAdvertisedList ? 'selected' : 'none'}>
<input
type="checkbox"
on:change={toggleUseNonAdvertisedList}
checked={useNonAdvertisedList}
/>
<strong>useNonAdvertisedList?</strong>
</label>
<label class={isUserSignedIn ? 'selected' : 'none'}>
<input
type="checkbox"
on:change={toggleIsUserSignedIn}
checked={isUserSignedIn}
/>
<strong>isUserSignedIn?</strong>
</label>
</nav>
<div id="consent-state">
Expand Down
25 changes: 22 additions & 3 deletions libs/@guardian/libs/src/consent-management-platform/cmp.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { CountryCode } from '../index.test';
import { getCurrentFramework } from './getCurrentFramework';
import { getIsConsentOrPay } from './isConsentOrPay';
import { mark } from './lib/mark';
import {
PRIVACY_MANAGER_AUSTRALIA,
PRIVACY_MANAGER_TCFV2,
PRIVACY_MANAGER_TCFV2_CONSENT_OR_PAY,
PRIVACY_MANAGER_USNAT,
} from './lib/sourcepointConfig';
import {
Expand All @@ -16,9 +19,21 @@ import type {
WillShowPrivacyMessage,
} from './types';

const init = (framework: ConsentFramework, pubData?: PubData): void => {
const init = (
framework: ConsentFramework,
countryCode: CountryCode,
isUserSignedIn: boolean,
useNonAdvertisedList: boolean,
pubData?: PubData,
): void => {
mark('cmp-init');
initSourcepoint(framework, pubData);
initSourcepoint(
framework,
countryCode,
isUserSignedIn,
useNonAdvertisedList,
pubData,
);
};

const willShowPrivacyMessage: WillShowPrivacyMessage = () =>
Expand All @@ -27,7 +42,11 @@ const willShowPrivacyMessage: WillShowPrivacyMessage = () =>
function showPrivacyManager(): void {
switch (getCurrentFramework()) {
case 'tcfv2':
window._sp_?.gdpr?.loadPrivacyManagerModal?.(PRIVACY_MANAGER_TCFV2);
window._sp_?.gdpr?.loadPrivacyManagerModal?.(
getIsConsentOrPay()
? PRIVACY_MANAGER_TCFV2_CONSENT_OR_PAY
: PRIVACY_MANAGER_TCFV2,
);
break;
case 'usnat':
window._sp_?.usnat?.loadPrivacyManagerModal?.(PRIVACY_MANAGER_USNAT);
Expand Down
15 changes: 13 additions & 2 deletions libs/@guardian/libs/src/consent-management-platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ const initialised = new Promise((resolve) => {
resolveInitialised = resolve;
});

const init: InitCMP = ({ pubData, country }) => {
const init: InitCMP = ({
pubData,
country,
isUserSignedIn = false,
useNonAdvertisedList = false,
}) => {
if (isDisabled() || isServerSide) {
return;
}
Expand Down Expand Up @@ -61,7 +66,13 @@ const init: InitCMP = ({ pubData, country }) => {

const framework = getFramework(country);

UnifiedCMP.init(framework, pubData ?? {});
UnifiedCMP.init(
framework,
country,
isUserSignedIn,
useNonAdvertisedList,
pubData ?? {},
);

void UnifiedCMP.willShowPrivacyMessage().then((willShowValue) => {
_willShowPrivacyMessage = willShowValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { isConsentOrPayCountry } from './isConsentOrPay.ts';

describe('isConsentOrPay', () => {
test('should return false country code is FR', () => {
expect(isConsentOrPayCountry('FR')).toBe(false);
});

test('should return true country code is GB', () => {
expect(isConsentOrPayCountry('GB')).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { CountryCode } from '../index.test';
import { isObject } from '../isObject/isObject';
import { storage } from '../storage/storage';
import { consentOrPayCountries } from './lib/sourcepointConfig';
import type { Participations } from './types';

let isConsentOrPay = false;

export const setIsConsentOrPay = (_isConsentOrPay: boolean) => {
isConsentOrPay = _isConsentOrPay;
};

export const getIsConsentOrPay = (): boolean => {
return isConsentOrPay;
};

export const isConsentOrPayCountry = (countryCode: CountryCode) => {
return consentOrPayCountries.includes(countryCode);
};

export const isInConsentOrPayABTest = (): boolean => {
const participations: Participations = storage.local.get(
'gu.ab.participations',
) as Participations;
return isObject(participations)
? participations.ConsentOrPayBanner?.variant === 'activate'
: false;
};
11 changes: 6 additions & 5 deletions libs/@guardian/libs/src/consent-management-platform/lib/ophan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
OphanComponentEvent,
OphanRecordFunction,
} from '../../ophan/@types';
import { getIsConsentOrPay } from '../isConsentOrPay';
import { SourcePointChoiceTypes } from './sourcepointConfig';

export type SourcepointButtonActions =
Expand All @@ -11,7 +12,7 @@ export type SourcepointButtonActions =
| 'dismiss'
| undefined;

export type SourcepointMessageType = 'ACCEPT_REJECT';
export type SourcepointMessageType = 'ACCEPT_REJECT' | 'CONSENT_OR_PAY_BANNER';
const getOphanRecordFunction = (): OphanRecordFunction => {
const record = window.guardian?.ophan?.record;

Expand All @@ -22,10 +23,10 @@ const getOphanRecordFunction = (): OphanRecordFunction => {
return () => {};
};

export const constructBannerMessageId = (
messageType: SourcepointMessageType,
messageId: string,
): string => {
export const constructBannerMessageId = (messageId: string): string => {
const messageType: SourcepointMessageType = getIsConsentOrPay()
? 'CONSENT_OR_PAY_BANNER'
: 'ACCEPT_REJECT';
return `${messageType}-${messageId}`;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import { isGuardianDomain } from './domain';

export const ACCOUNT_ID = 1257;
export const PRIVACY_MANAGER_USNAT = 1068329;
export const PROPERTY_ID = 7417;

export const PROPERTY_ID_MAIN = 9398; // TO BE CHANGED to 7417
export const PROPERTY_ID_SUBDOMAIN = 38161;
export const PROPERTY_ID_AUSTRALIA = 13348;

export const PROPERTY_HREF_SUBDOMAIN = 'http://subdomain.theguardian.com';
export const PROPERTY_HREF_MAIN = 'http://ui-dev';

export const PRIVACY_MANAGER_TCFV2 = 106842;
export const PRIVACY_MANAGER_TCFV2_CONSENT_OR_PAY = 1251121;
export const PRIVACY_MANAGER_AUSTRALIA = 1178486;

export const ENDPOINT = isGuardianDomain()
Expand All @@ -19,3 +26,5 @@ export const SourcePointChoiceTypes = {
RejectAll: 13,
Dismiss: 15,
} as const;

export const consentOrPayCountries = ['GB'];
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { getCookie } from '../cookies/getCookie';
import {
PROPERTY_ID_MAIN,
PROPERTY_ID_SUBDOMAIN,
} from './lib/sourcepointConfig';
import { postCustomConsent } from './tcfv2/api';
import type { SPUserConsent } from './types/tcfv2';

const purposeIdToNonAdvertisingPurposesMap = new Map<number, string>([
[1, '677e3387b265dc07332909da'], // 1
[2, '677e3386b265dc073328f686'], //2
[3, '677e3386b265dc073328f96f'], // 3
[4, '677e3386b265dc073328fc01'], //4
[5, '677e3386b265dc073328fe72'], // 5
[6, '677e3386b265dc073328ff70'], //6
[7, '677e3386b265dc0733290050'], //7
[8, '677e3386b265dc07332903c8'], //8
[9, '677e3386b265dc0733290502'], //9
[10, '677e3386b265dc073329071a'], //10
[11, '677e3386b265dc07332909c2'], //11
]);

const spBaseUrl = 'https://cdn.privacy-mgmt.com/consent/tcfv2/consent/v3';
interface Vendors {
_id: string;
name: string;
vendorType: string;
googleId?: string;
}

interface LegIntCategories {
_id: string;
name: string;
iabPurposeRef: {
iabId: number;
name: string;
};
}
interface UserConsentStatus {
vendors: Vendors[];
legIntCategories: LegIntCategories[];
legIntVendors: Vendors[];
categories: LegIntCategories[];
}

/**
* THis function gets the user's consent for the larger gdpr vendor list
* then calls postUserConsent
* https://sourcepoint-public-api.readme.io/reference/get_consent-v3-history-siteid-1
*/
export const mergeVendorList = async (): Promise<void> => {
const userConsent = await getUserConsentOnAdvertisingList();
await postUserConsent(userConsent);
await mergeUserConsent();
window.location.reload();
};

/**
*
*
* @return {*} {Promise<UserConsentStatus[]>}
*/
const getUserConsentOnAdvertisingList = async (): Promise<
UserConsentStatus[]
> => {
const consentUUID = getCookie({ name: 'consentUUID' });
const url = `${spBaseUrl}/history/${PROPERTY_ID_MAIN}?consentUUID=${consentUUID}`;

const getUserConsentOnAdvertisingListResponse = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});

return (await getUserConsentOnAdvertisingListResponse.json()) as UserConsentStatus[];
};

/**
*
*
* @param {UserConsentStatus[]} data
*/
const postUserConsent = async (data: UserConsentStatus[]): Promise<void> => {
const vendorIds = data[0]?.vendors.map((vendor) => vendor._id);
let purposeIds = data[0]?.categories.map(
(category) =>
purposeIdToNonAdvertisingPurposesMap.get(category.iabPurposeRef.iabId) ??
'',
);
let legitimateInterestPurposeIds = data[0]?.legIntCategories.map(
(category) =>
purposeIdToNonAdvertisingPurposesMap.get(category.iabPurposeRef.iabId) ??
'',
);

purposeIds = purposeIds?.filter((id) => id !== '');
legitimateInterestPurposeIds = legitimateInterestPurposeIds?.filter(
(id) => id !== '',
);

if (vendorIds && purposeIds && legitimateInterestPurposeIds) {
await postCustomConsent(
vendorIds,
purposeIds,
legitimateInterestPurposeIds,
);
}
};

/**
* This function merges the main vendor list with the sub-domain user consent status
* https://sourcepoint-public-api.readme.io/reference/post_consent-v3-siteid-tcstring
*/
const mergeUserConsent = async (): Promise<void> => {
const consentUUID = getCookie({ name: 'consentUUID' });
const url = `${spBaseUrl}/${PROPERTY_ID_SUBDOMAIN}/tcstring?consentUUID=${consentUUID}`;
const spUserConsentString = localStorage.getItem(
`_sp_user_consent_${PROPERTY_ID_MAIN}`,
);
const userConsent = JSON.parse(spUserConsentString ?? '{}') as SPUserConsent;

await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
euconsent: userConsent.gdpr?.euconsent,
}),
});
};
Loading

0 comments on commit 184a8a9

Please sign in to comment.