From 1fa00b430b145c4ca429b9674de1590d5d5c2762 Mon Sep 17 00:00:00 2001 From: joshua-ostrom Date: Fri, 3 May 2024 12:46:11 -0400 Subject: [PATCH 1/6] Support for GPC --- .github/workflows/release_cookie_manager.yml | 4 ++ packages/cookie-banner/package.json | 2 +- packages/cookie-manager/CHANGELOG.md | 5 +++ packages/cookie-manager/package.json | 2 +- .../cookie-manager/src/CookieContext.test.tsx | 2 +- packages/cookie-manager/src/CookieContext.tsx | 36 ++++++++++++--- .../src/utils/applyGpcToAdPref.test.ts | 14 ++++++ .../src/utils/applyGpcToAdPref.ts | 31 +++++++++++++ .../src/utils/applyGpcToCookiePref.test.ts | 44 +++++++++++++++++++ .../src/utils/applyGpcToCookiePref.ts | 24 ++++++++++ yarn.lock | 36 --------------- 11 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts create mode 100644 packages/cookie-manager/src/utils/applyGpcToAdPref.ts create mode 100644 packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts create mode 100644 packages/cookie-manager/src/utils/applyGpcToCookiePref.ts diff --git a/.github/workflows/release_cookie_manager.yml b/.github/workflows/release_cookie_manager.yml index 9cfefbc..bcc3810 100644 --- a/.github/workflows/release_cookie_manager.yml +++ b/.github/workflows/release_cookie_manager.yml @@ -21,6 +21,10 @@ jobs: yarn install yarn build + - name: Test cookie-manager + run: | + yarn test + - name: Publish cookie-manager working-directory: ./packages/cookie-manager run: | diff --git a/packages/cookie-banner/package.json b/packages/cookie-banner/package.json index 28c005d..c49fd7e 100644 --- a/packages/cookie-banner/package.json +++ b/packages/cookie-banner/package.json @@ -26,7 +26,7 @@ "react-dom": "^18.1.0" }, "dependencies": { - "@coinbase/cookie-manager": "^1.1.2", + "@coinbase/cookie-manager": "^1.1.3", "react-intl": "^6.5.1", "styled-components": "^5.3.6" } diff --git a/packages/cookie-manager/CHANGELOG.md b/packages/cookie-manager/CHANGELOG.md index 6ebca49..99a00d8 100644 --- a/packages/cookie-manager/CHANGELOG.md +++ b/packages/cookie-manager/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.1.3 (05/03/2024) + +- Added logic to honor GCP in non-EU localities +- Fixed failing spec + ## 1.1.2 (02/26/2024) #### 🚀 Updates diff --git a/packages/cookie-manager/package.json b/packages/cookie-manager/package.json index ebb2188..eb9d92f 100644 --- a/packages/cookie-manager/package.json +++ b/packages/cookie-manager/package.json @@ -1,6 +1,6 @@ { "name": "@coinbase/cookie-manager", - "version": "1.1.2", + "version": "1.1.3", "description": "Coinbase Cookie Manager", "main": "dist/index.js", "license": "Apache-2.0", diff --git a/packages/cookie-manager/src/CookieContext.test.tsx b/packages/cookie-manager/src/CookieContext.test.tsx index 66b278d..7571096 100644 --- a/packages/cookie-manager/src/CookieContext.test.tsx +++ b/packages/cookie-manager/src/CookieContext.test.tsx @@ -78,7 +78,7 @@ describe('CookieContext', () => { expect(onPreferenceChange).toHaveBeenCalledTimes(1); }); - it('does not remove cookies in shadow mode', async () => { + xit('does not remove cookies in shadow mode', async () => { const remove = jest.fn(); shadowMode = true; const mockGet = Cookies.get as jest.MockedFunction; diff --git a/packages/cookie-manager/src/CookieContext.tsx b/packages/cookie-manager/src/CookieContext.tsx index f65a454..c48435c 100644 --- a/packages/cookie-manager/src/CookieContext.tsx +++ b/packages/cookie-manager/src/CookieContext.tsx @@ -19,6 +19,8 @@ import { TrackerType, TrackingPreference, } from './types'; +import applyGpcToAdPref from './utils/applyGpcToAdPref'; +import applyGpcToCookiePref from './utils/applyGpcToCookiePref'; import getAllCookies, { areRecordsEqual } from './utils/getAllCookies'; import getDefaultTrackingPreference from './utils/getDefaultTrackingPreference'; import { getDomainWithoutSubdomain, getHostname } from './utils/getDomain'; @@ -40,6 +42,7 @@ export const CookieProvider = ({ children }: Props) => { const POLL_INTERVAL = 500; const [cookieValues, setCookieValues] = useState(() => getAllCookies()); + let priorCookieValue: Record; let trackingPreference: TrackingPreference; let adTrackingPreference: AdTrackingPreference; @@ -62,10 +65,12 @@ export const CookieProvider = ({ children }: Props) => { if (typeof window !== 'undefined') { const checkCookies = () => { const currentCookie = getAllCookies(); - if (!areRecordsEqual(cookieValues, currentCookie)) { + + if (priorCookieValue == undefined || !areRecordsEqual(priorCookieValue, currentCookie)) { + priorCookieValue = currentCookie; setCookieValues(currentCookie); trackingPreference = getTrackingPreference(currentCookie, region, config); - adTrackingPreference = getAdTrackingPreference(currentCookie); + adTrackingPreference = getAdTrackingPreference(currentCookie, region); setGTMVariables(trackingPreference, adTrackingPreference); const cookiesToRemove: Array = []; Object.keys(currentCookie).forEach((c) => { @@ -195,14 +200,31 @@ const getTrackingPreference = ( region === Region.EU ? cookieCache[EU_CONSENT_PREFERENCES_COOKIE] : cookieCache[DEFAULT_CONSENT_PREFERENCES_COOKIE]; - return trackingPreference || getDefaultTrackingPreference(region, config); -}; -const adTrackingDefault = { value: 'true' }; + // Example preference + // { region: Region.EU, consent: ['necessary'] } + const preference = trackingPreference || getDefaultTrackingPreference(region, config); + // Apply GPC when present + return applyGpcToCookiePref(preference); +}; -const getAdTrackingPreference = (cookieCache: Record): AdTrackingPreference => { +// Do we want to change the ADVERTISING_SHARING_ALLOWED value to clear prior values? +const getAdTrackingPreference = ( + cookieCache: Record, + region: Region +): AdTrackingPreference => { const adTrackingPreference = cookieCache[ADVERTISING_SHARING_ALLOWED]; - return adTrackingPreference || adTrackingDefault; + + // TODO if we want to support GPC + + // Currently this defaults to true for all regions... awaiting word from privacy / legal + // if we want to default to false for EU + const adTrackingDefault = region === Region.EU ? { value: 'false' } : { value: 'true' }; + + // Example adPreference { value: 'false' } + const adPreference = adTrackingPreference || adTrackingDefault; + + return applyGpcToAdPref(region, adPreference); }; export const useCookie = (cookieName: string): [any | undefined, SetCookieFunction] => { diff --git a/packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts b/packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts new file mode 100644 index 0000000..6c2bd94 --- /dev/null +++ b/packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts @@ -0,0 +1,14 @@ +import { Region } from '../types'; +import applyGpcToAdPref from './applyGpcToAdPref'; + +describe('applyGpcToAdPref', () => { + it('removes targeting when GPC is ON in non-EU', () => { + (navigator as any).globalPrivacyControl = true; + expect(applyGpcToAdPref(Region.DEFAULT, { value: true })).toEqual({ value: false }); + }); + + it('ignores GPC when in EU', () => { + (navigator as any).globalPrivacyControl = true; + expect(applyGpcToAdPref(Region.EU, { value: true })).toEqual({ value: true }); + }); +}); diff --git a/packages/cookie-manager/src/utils/applyGpcToAdPref.ts b/packages/cookie-manager/src/utils/applyGpcToAdPref.ts new file mode 100644 index 0000000..d1f57cb --- /dev/null +++ b/packages/cookie-manager/src/utils/applyGpcToAdPref.ts @@ -0,0 +1,31 @@ +import { AdTrackingPreference, Region } from '../types'; + +const applyGpcToAdPref = ( + region: Region, + preference: AdTrackingPreference +): AdTrackingPreference => { + // We are only applying GPC in non-EU countries at this point + if (region == Region.EU) { + return preference; + } + // If we lack GPC or it's set ot false we are done + if (!(navigator as any).globalPrivacyControl) { + return preference; + } + + // If the user already has sharing turned off nothing to do here + if (preference.value == false) { + return preference; // already allowing sharing + } + + // We could set the updated at time to now if we'd like + // preference.updated_at = new Date().getTime(); + + const pref: AdTrackingPreference = preference.updated_at + ? { value: false, updated_at: preference.updated_at } + : { value: false }; + + return pref; +}; + +export default applyGpcToAdPref; diff --git a/packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts b/packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts new file mode 100644 index 0000000..fb254cb --- /dev/null +++ b/packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts @@ -0,0 +1,44 @@ +import { Region, TrackingCategory } from '../types'; +import applyGpcToCookiePref from './applyGpcToCookiePref'; + +describe('applyGpcToCookiePref', () => { + it('removes targeting when GPC is ON in non-EU', () => { + (navigator as any).globalPrivacyControl = true; + expect( + applyGpcToCookiePref({ region: Region.DEFAULT, consent: [TrackingCategory.TARGETING] }) + ).toEqual({ + region: Region.DEFAULT, + consent: [], + }); + }); + + it('does not remove targeting when GPC is ON in EU', () => { + (navigator as any).globalPrivacyControl = true; + expect( + applyGpcToCookiePref({ region: Region.EU, consent: [TrackingCategory.TARGETING] }) + ).toEqual({ + region: Region.EU, + consent: [TrackingCategory.TARGETING], + }); + }); + + it('retains targeting when GPC is OFF', () => { + (navigator as any).globalPrivacyControl = false; + expect( + applyGpcToCookiePref({ region: Region.DEFAULT, consent: [TrackingCategory.TARGETING] }) + ).toEqual({ + region: Region.DEFAULT, + consent: [TrackingCategory.TARGETING], + }); + }); + + it('retains targeting when GPC is undefined', () => { + delete (navigator as any).globalPrivacyControl; + expect( + applyGpcToCookiePref({ region: Region.DEFAULT, consent: [TrackingCategory.TARGETING] }) + ).toEqual({ + region: Region.DEFAULT, + consent: [TrackingCategory.TARGETING], + }); + }); +}); diff --git a/packages/cookie-manager/src/utils/applyGpcToCookiePref.ts b/packages/cookie-manager/src/utils/applyGpcToCookiePref.ts new file mode 100644 index 0000000..faab824 --- /dev/null +++ b/packages/cookie-manager/src/utils/applyGpcToCookiePref.ts @@ -0,0 +1,24 @@ +import { Region, TrackingCategory, TrackingPreference } from '../types'; + +// { region: Region.DEFAULT, consent: ['necessary', 'performance', 'functional', 'targeting'] } +const applyGpcToCookiePref = (preference: TrackingPreference): TrackingPreference => { + // We are only applying GPC in non-EU countries at this point + if (preference.region == Region.EU) { + return preference; + } + + if (!(navigator as any).globalPrivacyControl) { + return preference; + } + // If the user had opted in to GPC we want to honor it + const categories = preference.consent.filter((cat) => cat !== TrackingCategory.TARGETING); + + if (categories == preference.consent) { + return preference; + } + const pref = { region: preference.region, consent: categories }; + + return pref; +}; + +export default applyGpcToCookiePref; diff --git a/yarn.lock b/yarn.lock index 1f1dc8c..28d74a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1077,42 +1077,6 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@coinbase/cookie-banner@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@coinbase/cookie-banner/-/cookie-banner-1.0.1.tgz#90a10f13b62356baca41117cbcf98a20bff7e1cd" - integrity sha512-tVgNjNaSCC7nts/uju+vWkucWyXPSL4CaZflc5rkLuEKb7ZnY0otZnqDPRx0O4/0xLQ72tY9nw41/+HhTsKOqg== - dependencies: - "@coinbase/cookie-manager" "^1.0.0" - react-intl "^6.5.1" - styled-components "^5.3.6" - -"@coinbase/cookie-banner@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@coinbase/cookie-banner/-/cookie-banner-1.0.3.tgz#a755e4ac9ffa0f3bfe22fc84ec4d88863ad82ad3" - integrity sha512-RMCyb42Ja4vxdZlN8tsFQaQgZUJwx7yvSFZeMnArQyHlKOjpzvJ+NCXY3G4aVYEGC0j86otsZ5Xe43F+qs2MYw== - dependencies: - "@coinbase/cookie-manager" "1.1.1" - react-intl "^6.5.1" - styled-components "^5.3.6" - -"@coinbase/cookie-manager@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@coinbase/cookie-manager/-/cookie-manager-1.1.0.tgz#3a47a89989953e0cb32b6b63445879252e42477b" - integrity sha512-r8UR7jSYxAPKIV7jSlqkmWfWi7kdcfMo7hJ0dV0FF2wMx1IIMU6V72BmuMpmn7Ov7HizAKtEcl/I/9fSWRVIQw== - dependencies: - "@coinbase/cookie-banner" "1.0.1" - "@coinbase/cookie-manager" "1.1.0" - js-cookie "^3.0.5" - -"@coinbase/cookie-manager@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@coinbase/cookie-manager/-/cookie-manager-1.1.1.tgz#f204ade281a2e2dccdf6e77baa7433cd054656a4" - integrity sha512-1fjLrWOyM2392eaDdgqIHlZHGuziRRzQZib3RuYSTdrX9z81muDc/oSvakb6VeDtfZkje0+3MHhnkSscaa5tUg== - dependencies: - "@coinbase/cookie-banner" "1.0.1" - "@coinbase/cookie-manager" "1.1.0" - js-cookie "^3.0.5" - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" From e5019146140b2a7772c58f9fd702b56a6650b86e Mon Sep 17 00:00:00 2001 From: joshua-ostrom Date: Fri, 3 May 2024 13:24:31 -0400 Subject: [PATCH 2/6] Unit tests --- .github/workflows/node.js.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 9cebfba..413190d 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -27,3 +27,5 @@ jobs: - name: Lint Check # When fails, please run "yarn lint" to your code run: yarn lint + - name: Unit tests + run: yarn build && yarn test From 9eb4e779dacb19e0d5bb17cf4d48d263d672c12e Mon Sep 17 00:00:00 2001 From: joshua-ostrom Date: Fri, 3 May 2024 13:28:30 -0400 Subject: [PATCH 3/6] Enabled disable test --- packages/cookie-manager/src/CookieContext.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cookie-manager/src/CookieContext.test.tsx b/packages/cookie-manager/src/CookieContext.test.tsx index 7571096..66b278d 100644 --- a/packages/cookie-manager/src/CookieContext.test.tsx +++ b/packages/cookie-manager/src/CookieContext.test.tsx @@ -78,7 +78,7 @@ describe('CookieContext', () => { expect(onPreferenceChange).toHaveBeenCalledTimes(1); }); - xit('does not remove cookies in shadow mode', async () => { + it('does not remove cookies in shadow mode', async () => { const remove = jest.fn(); shadowMode = true; const mockGet = Cookies.get as jest.MockedFunction; From 5aeefa9f520ad0927003dc2c4e9f28aafa81c66b Mon Sep 17 00:00:00 2001 From: joshua-ostrom Date: Fri, 3 May 2024 13:30:00 -0400 Subject: [PATCH 4/6] Cleaned up comments --- packages/cookie-manager/src/CookieContext.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/cookie-manager/src/CookieContext.tsx b/packages/cookie-manager/src/CookieContext.tsx index c48435c..0a80ded 100644 --- a/packages/cookie-manager/src/CookieContext.tsx +++ b/packages/cookie-manager/src/CookieContext.tsx @@ -215,13 +215,9 @@ const getAdTrackingPreference = ( ): AdTrackingPreference => { const adTrackingPreference = cookieCache[ADVERTISING_SHARING_ALLOWED]; - // TODO if we want to support GPC - - // Currently this defaults to true for all regions... awaiting word from privacy / legal - // if we want to default to false for EU const adTrackingDefault = region === Region.EU ? { value: 'false' } : { value: 'true' }; - // Example adPreference { value: 'false' } + // Example: adPreference { value: 'false' } const adPreference = adTrackingPreference || adTrackingDefault; return applyGpcToAdPref(region, adPreference); From 71ec6d4400f4099cd5edda66e59158fd0fe60240 Mon Sep 17 00:00:00 2001 From: joshua-ostrom Date: Fri, 3 May 2024 14:53:37 -0400 Subject: [PATCH 5/6] Update version for sample app --- apps/example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/example/package.json b/apps/example/package.json index a5db984..a154689 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@coinbase/cookie-banner": "1.0.4", - "@coinbase/cookie-manager": "1.1.2", + "@coinbase/cookie-manager": "1.1.3", "next": "14.0.0", "react": "^18", "react-dom": "^18" From 11236455b3a8d981e483718d3a17d15d2e0b096d Mon Sep 17 00:00:00 2001 From: joshua-ostrom Date: Mon, 6 May 2024 12:45:24 -0400 Subject: [PATCH 6/6] Apply GPC to cookie cache as well --- packages/cookie-manager/src/CookieContext.tsx | 12 +++--- .../src/utils/applyGpcToAdPref.test.ts | 2 +- .../src/utils/applyGpcToAdPref.ts | 2 +- .../src/utils/applyGpcToCookiePref.test.ts | 2 +- .../src/utils/applyGpcToCookiePref.ts | 2 +- .../src/utils/getAllCookies.test.ts | 38 +++++++++++++++++-- .../cookie-manager/src/utils/getAllCookies.ts | 34 +++++++++++++++-- 7 files changed, 76 insertions(+), 16 deletions(-) diff --git a/packages/cookie-manager/src/CookieContext.tsx b/packages/cookie-manager/src/CookieContext.tsx index 0a80ded..5ac61c3 100644 --- a/packages/cookie-manager/src/CookieContext.tsx +++ b/packages/cookie-manager/src/CookieContext.tsx @@ -19,8 +19,8 @@ import { TrackerType, TrackingPreference, } from './types'; -import applyGpcToAdPref from './utils/applyGpcToAdPref'; -import applyGpcToCookiePref from './utils/applyGpcToCookiePref'; +import { applyGpcToAdPref } from './utils/applyGpcToAdPref'; +import { applyGpcToCookiePref } from './utils/applyGpcToCookiePref'; import getAllCookies, { areRecordsEqual } from './utils/getAllCookies'; import getDefaultTrackingPreference from './utils/getDefaultTrackingPreference'; import { getDomainWithoutSubdomain, getHostname } from './utils/getDomain'; @@ -41,7 +41,7 @@ export const CookieProvider = ({ children }: Props) => { const { config, region, shadowMode, log, onPreferenceChange } = useTrackingManager(); const POLL_INTERVAL = 500; - const [cookieValues, setCookieValues] = useState(() => getAllCookies()); + const [cookieValues, setCookieValues] = useState(() => getAllCookies(region)); let priorCookieValue: Record; let trackingPreference: TrackingPreference; let adTrackingPreference: AdTrackingPreference; @@ -64,13 +64,16 @@ export const CookieProvider = ({ children }: Props) => { useEffect(() => { if (typeof window !== 'undefined') { const checkCookies = () => { - const currentCookie = getAllCookies(); + const currentCookie = getAllCookies(region); if (priorCookieValue == undefined || !areRecordsEqual(priorCookieValue, currentCookie)) { priorCookieValue = currentCookie; setCookieValues(currentCookie); + + // Grab out prefences (they wil have GPC applied if present) trackingPreference = getTrackingPreference(currentCookie, region, config); adTrackingPreference = getAdTrackingPreference(currentCookie, region); + setGTMVariables(trackingPreference, adTrackingPreference); const cookiesToRemove: Array = []; Object.keys(currentCookie).forEach((c) => { @@ -219,7 +222,6 @@ const getAdTrackingPreference = ( // Example: adPreference { value: 'false' } const adPreference = adTrackingPreference || adTrackingDefault; - return applyGpcToAdPref(region, adPreference); }; diff --git a/packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts b/packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts index 6c2bd94..23c19ff 100644 --- a/packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts +++ b/packages/cookie-manager/src/utils/applyGpcToAdPref.test.ts @@ -1,5 +1,5 @@ import { Region } from '../types'; -import applyGpcToAdPref from './applyGpcToAdPref'; +import { applyGpcToAdPref } from './applyGpcToAdPref'; describe('applyGpcToAdPref', () => { it('removes targeting when GPC is ON in non-EU', () => { diff --git a/packages/cookie-manager/src/utils/applyGpcToAdPref.ts b/packages/cookie-manager/src/utils/applyGpcToAdPref.ts index d1f57cb..df36dfc 100644 --- a/packages/cookie-manager/src/utils/applyGpcToAdPref.ts +++ b/packages/cookie-manager/src/utils/applyGpcToAdPref.ts @@ -28,4 +28,4 @@ const applyGpcToAdPref = ( return pref; }; -export default applyGpcToAdPref; +export { applyGpcToAdPref }; diff --git a/packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts b/packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts index fb254cb..f4a3d84 100644 --- a/packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts +++ b/packages/cookie-manager/src/utils/applyGpcToCookiePref.test.ts @@ -1,5 +1,5 @@ import { Region, TrackingCategory } from '../types'; -import applyGpcToCookiePref from './applyGpcToCookiePref'; +import { applyGpcToCookiePref } from './applyGpcToCookiePref'; describe('applyGpcToCookiePref', () => { it('removes targeting when GPC is ON in non-EU', () => { diff --git a/packages/cookie-manager/src/utils/applyGpcToCookiePref.ts b/packages/cookie-manager/src/utils/applyGpcToCookiePref.ts index faab824..3cda347 100644 --- a/packages/cookie-manager/src/utils/applyGpcToCookiePref.ts +++ b/packages/cookie-manager/src/utils/applyGpcToCookiePref.ts @@ -21,4 +21,4 @@ const applyGpcToCookiePref = (preference: TrackingPreference): TrackingPreferenc return pref; }; -export default applyGpcToCookiePref; +export { applyGpcToCookiePref }; diff --git a/packages/cookie-manager/src/utils/getAllCookies.test.ts b/packages/cookie-manager/src/utils/getAllCookies.test.ts index b215be4..aa23409 100644 --- a/packages/cookie-manager/src/utils/getAllCookies.test.ts +++ b/packages/cookie-manager/src/utils/getAllCookies.test.ts @@ -1,6 +1,7 @@ import Cookies from 'js-cookie'; -import getAllCookies from './getAllCookies'; +import getAllCookies, { Region } from './getAllCookies'; +export { Region } from '../types'; jest.mock('js-cookie', () => ({ __esModule: true, @@ -16,17 +17,48 @@ describe('getAllCookies', () => { const mockGet = Cookies.get as jest.MockedFunction; const value = { region: 'DEFAULT', - consent: ['necessary', 'performance'], + consent: ['necessary', 'performance', 'targeting'], }; const cookies = { cm_default_preferences: JSON.stringify(value), + advertising_sharing_allowed: JSON.stringify({ value: true }), some_cookie: 'iamastring', another_cookie: '5', array_cookie: JSON.stringify(['item1', 'item2']), }; + (navigator as any).globalPrivacyControl = false; + mockGet.mockImplementation(jest.fn(() => cookies)); - expect(getAllCookies({})).toEqual({ + + expect(getAllCookies(Region.DEFAULT, {})).toEqual({ cm_default_preferences: value, + advertising_sharing_allowed: { value: true }, + some_cookie: 'iamastring', + another_cookie: 5, + array_cookie: ['item1', 'item2'], + }); + }); + + it('applies GCP to cookie values', () => { + const mockGet = Cookies.get as jest.MockedFunction; + const value = { + region: 'DEFAULT', + consent: ['necessary', 'performance', 'targeting'], + }; + const cookies = { + cm_default_preferences: JSON.stringify(value), + advertising_sharing_allowed: JSON.stringify({ value: true }), + some_cookie: 'iamastring', + another_cookie: '5', + array_cookie: JSON.stringify(['item1', 'item2']), + }; + (navigator as any).globalPrivacyControl = true; + + mockGet.mockImplementation(jest.fn(() => cookies)); + + expect(getAllCookies(Region.DEFAULT, {})).toEqual({ + cm_default_preferences: { consent: ['necessary', 'performance'], region: 'DEFAULT' }, + advertising_sharing_allowed: { value: false }, some_cookie: 'iamastring', another_cookie: 5, array_cookie: ['item1', 'item2'], diff --git a/packages/cookie-manager/src/utils/getAllCookies.ts b/packages/cookie-manager/src/utils/getAllCookies.ts index f1bb57a..412a6cc 100644 --- a/packages/cookie-manager/src/utils/getAllCookies.ts +++ b/packages/cookie-manager/src/utils/getAllCookies.ts @@ -1,6 +1,17 @@ import Cookies from 'js-cookie'; -export const deserializeCookies = (cookies: Record) => { +export { Region } from '../types'; + +import { + ADVERTISING_SHARING_ALLOWED, + DEFAULT_CONSENT_PREFERENCES_COOKIE, + EU_CONSENT_PREFERENCES_COOKIE, +} from '../constants'; +import { Region } from '../types'; +import { applyGpcToAdPref } from './applyGpcToAdPref'; +import { applyGpcToCookiePref } from './applyGpcToCookiePref'; + +export const deserializeCookies = (region: Region, cookies: Record) => { const parsedCookies: Record = {}; Object.keys(cookies).forEach((c) => { @@ -9,15 +20,30 @@ export const deserializeCookies = (cookies: Record) => { } catch (e) { parsedCookies[c] = cookies[c]; } + parsedCookies[c] = filterCookieValue(region, c, parsedCookies[c]); }); return parsedCookies; }; -export default function getAllCookies(initialCookies?: Record) { +export default function getAllCookies(region: Region, initialCookies?: Record) { if (typeof window === 'undefined' && initialCookies) { - return deserializeCookies(initialCookies); + return deserializeCookies(region, initialCookies); + } + return deserializeCookies(region, Cookies.get() || {}); +} + +// Apply in in memory filters to the cookie values. Currently we are just apply +// Global Privacy Control (GPC) logic to ensure we are honoring GPC +function filterCookieValue(region: Region, cookieName: string, cookieValue: any) { + if (cookieName == ADVERTISING_SHARING_ALLOWED) { + cookieValue = applyGpcToAdPref(region, cookieValue); + } else if ( + (region == Region.DEFAULT && cookieName == DEFAULT_CONSENT_PREFERENCES_COOKIE) || + (region == Region.EU && cookieName == EU_CONSENT_PREFERENCES_COOKIE) + ) { + cookieValue = applyGpcToCookiePref(cookieValue); } - return deserializeCookies(Cookies.get() || {}); + return cookieValue; } export function areRecordsEqual(