diff --git a/docs/cookie-banner.html b/docs/cookie-banner.html index 58a6248a..cdb9faba 100644 --- a/docs/cookie-banner.html +++ b/docs/cookie-banner.html @@ -217,7 +217,6 @@

Usage

import banner from '@stormid/cookie-banner';
 
 const cookieBanner = banner({
-    tid: 'UA-XXXXXXXX-X',
     types: {
         'performance': {
             suggested: true, //set as pre-checked on consent form as a suggested response
@@ -255,7 +254,6 @@ 

Usage

Options

{
     name: '.CookiePreferences', //name of the cookie set to record user consent
-    tid: '', // Google Analytics tracking id for Measurement API event tracking
     path: '/', //path of the preferences cookie
     domain: window.location.hostname === 'localhost' ? '' : `.${removeSubdomain(window.location.hostname)}`, //domain of the preferences cookie, defaults to .<root-domain>
     secure: true, //preferences cookie secure
diff --git a/packages/cookie-banner/README.md b/packages/cookie-banner/README.md
index 3ad8332b..3b543a11 100644
--- a/packages/cookie-banner/README.md
+++ b/packages/cookie-banner/README.md
@@ -4,8 +4,6 @@ GDPR compliant cookie banner and consent form.
 
 Renders a cookie banner and a consent form based on configuration settings, and conditionally invokes cookie-reliant functionality based on user consent.
 
-Optionally send anonymous [predefined cookie banner and consent form interaction measurements](./measurements.md) to a specified Google Analytics using the Google Measurement API.
-
 ---
 
 ## Usage
@@ -38,7 +36,6 @@ Initialise the module (example configuration shown below)
 import banner from '@stormid/cookie-banner';
 
 const cookieBanner = banner({
-    tid: 'UA-XXXXXXXX-X',
     types: {
         'performance': {
             suggested: true, //set as pre-checked on consent form as a suggested response
@@ -78,7 +75,6 @@ const cookieBanner = banner({
 ```
 {
     name: '.CookiePreferences', //name of the cookie set to record user consent
-    tid: '', // Google Analytics tracking id for Measurement API event tracking
     path: '/', //path of the preferences cookie
     domain: window.location.hostname === 'localhost' ? '' : `.${removeSubdomain(window.location.hostname)}`, //domain of the preferences cookie, defaults to .
     secure: true, //preferences cookie secure
diff --git a/packages/cookie-banner/__tests__/banner/index.js b/packages/cookie-banner/__tests__/banner/index.js
index c1fc6341..68ff38a2 100644
--- a/packages/cookie-banner/__tests__/banner/index.js
+++ b/packages/cookie-banner/__tests__/banner/index.js
@@ -7,7 +7,6 @@ const init = () => {
     cookieBanner({
         secure: false,
         hideBannerOnFormPage: false,
-        tid: 'UA-XXXXX-Y',
         types: {
             test: {
                 title: 'Test title',
@@ -51,7 +50,6 @@ describe(`Cookie banner > DOM > not render`, () => {
         cookieBanner({
             secure: false,
             hideBannerOnFormPage: true,
-            tid: 'UA-XXXXX-Y',
             types: {
                 test: {
                     title: 'Test title',
diff --git a/packages/cookie-banner/__tests__/banner/showBanner.js b/packages/cookie-banner/__tests__/banner/showBanner.js
index 14ff4b84..04ed3741 100644
--- a/packages/cookie-banner/__tests__/banner/showBanner.js
+++ b/packages/cookie-banner/__tests__/banner/showBanner.js
@@ -8,7 +8,6 @@ const init = () => {
     document.body.innerHTML = `
`; instance = cookieBanner({ secure: false, - tid: 'UA-XXXXX-Y', types: { test: { title: 'Test title', diff --git a/packages/cookie-banner/__tests__/consent.js b/packages/cookie-banner/__tests__/consent.js index 8047e5a5..4d971c43 100644 --- a/packages/cookie-banner/__tests__/consent.js +++ b/packages/cookie-banner/__tests__/consent.js @@ -29,7 +29,7 @@ describe(`Cookie banner > consent > callback`, () => { } }; - const state = { settings: {tid: 'UA-XXXXX-Y', types}, consent: { test: 1, test2: 0 }}; + const state = { settings: { types }, consent: { test: 1, test2: 0 }}; applyEffects(state); expect(document.getElementById('test').textContent).toEqual("Consent given"); expect(document.getElementById('test2').textContent).toEqual(""); diff --git a/packages/cookie-banner/__tests__/cookies.js b/packages/cookie-banner/__tests__/cookies.js index 4eac118c..09e30f0d 100644 --- a/packages/cookie-banner/__tests__/cookies.js +++ b/packages/cookie-banner/__tests__/cookies.js @@ -7,7 +7,6 @@ const init = () => { window.__cb__ = cookieBanner({ secure: false, hideBannerOnFormPage: false, - tid: 'UA-141774857-1', types: { test: { title: 'Test title', @@ -41,15 +40,13 @@ describe(`Cookie banner > cookies > update`, () => { it('Sets a cookie based on preferences form', async () => { document.querySelector(`.${defaults.classNames.acceptBtn}`).click(); - //get the cid from state - const cid = window.__cb__.getState().persistentMeasurementParams.cid; - expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":1,"performance":1},"cid":"${cid}"}`)}`); + expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":1,"performance":1}}`)}`); const fields = Array.from(document.querySelectorAll(`.${defaults.classNames.field}`)); fields[1].checked = true; fields[3].checked = true; document.querySelector(`.${defaults.classNames.submitBtn}`).click(); - expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":0,"performance":0},"cid":"${cid}"}`)}`); + expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":0,"performance":0}}`)}`); }); }); diff --git a/packages/cookie-banner/__tests__/form/api.js b/packages/cookie-banner/__tests__/form/api.js index a7bf0e64..e9a86f95 100644 --- a/packages/cookie-banner/__tests__/form/api.js +++ b/packages/cookie-banner/__tests__/form/api.js @@ -8,7 +8,6 @@ describe(`Cookie banner > DOM > form > render by api`, () => { const instance = cookieBanner({ secure: false, hideBannerOnFormPage: false, - tid: 'UA-XXXXX-Y', types: { test: { suggested: true, diff --git a/packages/cookie-banner/__tests__/form/interactions.js b/packages/cookie-banner/__tests__/form/interactions.js index b37f47bb..3db92465 100644 --- a/packages/cookie-banner/__tests__/form/interactions.js +++ b/packages/cookie-banner/__tests__/form/interactions.js @@ -13,7 +13,6 @@ describe(`Cookie banner > DOM > form interactions`, () => { window.__cb__ = cookieBanner({ secure: false, hideBannerOnFormPage: false, - tid: 'UA-141774857-1', types: { test: { title: 'Test title', @@ -59,9 +58,7 @@ describe(`Cookie banner > DOM > form interactions`, () => { it('Submit button should set the cookie and hide the banner', async () => { document.querySelector(`.${defaults.classNames.acceptBtn}`).click(); - //get the cid from state - const cid = window.__cb__.getState().persistentMeasurementParams.cid; - expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":1,"performance":1},"cid":"${cid}"}`)}`); + expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":1,"performance":1}}`)}`); expect(document.querySelector(`.${defaults.classNames.banner}`)).toBeNull(); }); }); \ No newline at end of file diff --git a/packages/cookie-banner/__tests__/form/not-render.js b/packages/cookie-banner/__tests__/form/not-render.js index c1c84f69..2bd8532a 100644 --- a/packages/cookie-banner/__tests__/form/not-render.js +++ b/packages/cookie-banner/__tests__/form/not-render.js @@ -4,7 +4,6 @@ import defaults from '../../src/lib/defaults'; describe(`Cookie banner > DOM > form > not render`, () => { document.body.innerHTML = `
`; cookieBanner({ - tid: 'UA-XXXXX-Y', types: { test: { title: 'Test title', diff --git a/packages/cookie-banner/__tests__/form/render.js b/packages/cookie-banner/__tests__/form/render.js index 69f05afd..2d32f727 100644 --- a/packages/cookie-banner/__tests__/form/render.js +++ b/packages/cookie-banner/__tests__/form/render.js @@ -7,7 +7,6 @@ describe(`Cookie banner > DOM > form > render`, () => { cookieBanner({ secure: false, hideBannerOnFormPage: false, - tid: 'UA-XXXXX-Y', types: { test: { suggested: true, diff --git a/packages/cookie-banner/__tests__/measure/init.js b/packages/cookie-banner/__tests__/measure/init.js deleted file mode 100644 index 9395643d..00000000 --- a/packages/cookie-banner/__tests__/measure/init.js +++ /dev/null @@ -1,53 +0,0 @@ -import cookieBanner from '../../src'; -import defaults from '../../src/lib/defaults'; - -describe(`Cookie banner > measure > init`, () => { - - it('It should console.warn if no tid is supplied', async () => { - console.warn = jest.fn(); - document.body.innerHTML = `
`; - cookieBanner({ - tid: '', - secure: false, - types: { - test: { - title: 'Test title', - description: 'Test description', - labels: { - yes: 'Pages you visit and actions you take will be measured and used to improve the service', - no: 'Pages you visit and actions you take will not be measured and used to improve the service' - }, - fns: [ - () => { } - ] - } - } - }); - expect(console.warn).toBeCalledWith('The tid setting is missing. A tid is required for banner measurements.'); - }); - - it('should not render the banner if preferences are saved but there is no cid, and no tid has been provided in settings', () => { - //set preference cookies, but not cid - document.cookie = `${defaults.name}=${btoa(`{"consent":{"test":1}}`)}`; - document.body.innerHTML = `
`; - cookieBanner({ - tid: '', - secure: false, - types: { - test: { - title: 'Test title', - description: 'Test description', - labels: { - yes: 'Pages you visit and actions you take will be measured and used to improve the service', - no: 'Pages you visit and actions you take will not be measured and used to improve the service' - }, - fns: [ - () => { } - ] - } - } - }); - expect(document.querySelector(`.${defaults.classNames.banner}`)).toBeNull(); - }) - -}); \ No newline at end of file diff --git a/packages/cookie-banner/__tests__/measure/measurements/accept-and-update.js b/packages/cookie-banner/__tests__/measure/measurements/accept-and-update.js deleted file mode 100644 index be5caa94..00000000 --- a/packages/cookie-banner/__tests__/measure/measurements/accept-and-update.js +++ /dev/null @@ -1,75 +0,0 @@ -import cookieBanner from '../../../src'; -import { composeParams, dataToURL, composeMeasurementConsent } from '../../../src/lib/measurement'; -import { MEASUREMENTS } from '../../../src/lib/constants'; -import defaults from '../../../src/lib/defaults'; - -navigator = navigator || {}; -navigator.sendBeacon = jest.fn(); - -describe('Cookie banner > measure > banner/form/accept/change', () => { - - //user loads screen containing consent form, clicks banner accept - it('should send banner display, form display, banner accept, and form submit beacons', () => { - document.body.innerHTML = `
`; - const __cb__ = cookieBanner({ - debug: true, - hideBannerOnFormPage: false, - secure: false, - tid: 'UA-141774857-1', - types: { - test: { - title: 'Test title', - description: 'Test description', - labels: { - yes: 'Pages you visit and actions you take will be measured and used to improve the service', - no: 'Pages you visit and actions you take will not be measured and used to improve the service' - }, - fns: [ - () => { } - ] - } - } - }); - const cid = __cb__.getState().persistentMeasurementParams.cid; - const bannerDisplayReqURL = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.BANNER_DISPLAY - }, 'collect'); - const formDisplayReqURL = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.FORM_DISPLAY - }, 'collect'); - - expect(navigator.sendBeacon).toHaveBeenCalled(); - //1. banner display tracked - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(1, bannerDisplayReqURL); - //2. form display tracked - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(2, formDisplayReqURL); - - //3. click accept on banner tracked - document.querySelector(`.${defaults.classNames.acceptBtn}`).click(); - const bannerAcceptUrl = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.BANNER_ACCEPT, - cd2: composeMeasurementConsent(__cb__.getState().consent) - }, 'collect'); - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(3, bannerAcceptUrl); - - //4. Change preferences and submit form tracked - const fields = Array.from(document.querySelectorAll(`.${defaults.classNames.field}`)); - fields[1].checked = true; - document.querySelector(`.${defaults.classNames.submitBtn}`).click(); - - const consentString = composeMeasurementConsent(__cb__.getState().consent); - const preferenceSaveUrl = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.SAVE_PREFERENCES, - cd2: consentString === '' ? 'None' : consentString, - cm2: 0, - cm3: 0 - }, 'collect'); - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(4, preferenceSaveUrl); - - }); - -}); \ No newline at end of file diff --git a/packages/cookie-banner/__tests__/measure/measurements/options.js b/packages/cookie-banner/__tests__/measure/measurements/options.js deleted file mode 100644 index 7d643298..00000000 --- a/packages/cookie-banner/__tests__/measure/measurements/options.js +++ /dev/null @@ -1,47 +0,0 @@ -import cookieBanner from '../../../src'; -import { composeParams, dataToURL } from '../../../src/lib/measurement'; -import { MEASUREMENTS } from '../../../src/lib/constants'; - -navigator = navigator || {}; -navigator.sendBeacon = jest.fn(); - -describe('Cookie banner > measurements > options', () => { - - //user loads page clicks banner options - it('should send banner display, banner options beacons', () => { - document.body.innerHTML = `
`; - const __cb__ = cookieBanner({ - debug: true, - hideBannerOnFormPage: false, - secure: false, - tid: 'UA-141774857-1', - types: { - test: { - title: 'Test title', - description: 'Test description', - labels: { - yes: 'Pages you visit and actions you take will be measured and used to improve the service', - no: 'Pages you visit and actions you take will not be measured and used to improve the service' - }, - fns: [ - () => { } - ] - } - } - }); - const cid = __cb__.getState().persistentMeasurementParams.cid; - const bannerDisplayReqURL = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.BANNER_DISPLAY - }, 'collect'); - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(1, bannerDisplayReqURL); - - //2. click options on banner tracked - document.querySelector(`.${__cb__.getState().settings.classNames.optionsBtn}`).click(); - const bannerOptionsUrl = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.BANNER_OPTIONS - }, 'collect'); - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(2, bannerOptionsUrl); - }); -}); \ No newline at end of file diff --git a/packages/cookie-banner/__tests__/measure/measurements/update.js b/packages/cookie-banner/__tests__/measure/measurements/update.js deleted file mode 100644 index 522f3264..00000000 --- a/packages/cookie-banner/__tests__/measure/measurements/update.js +++ /dev/null @@ -1,74 +0,0 @@ -import cookieBanner from '../../../src'; -import { composeParams, dataToURL, composeMeasurementConsent } from '../../../src/lib/measurement'; -import { MEASUREMENTS } from '../../../src/lib/constants'; -import defaults from '../../../src/lib/defaults'; - -navigator = navigator || {}; -navigator.sendBeacon = jest.fn(); - -describe('Cookie banner > measure > update', () => { - - //user loads screen containing consent form, clicks banner accept - it('should send form display, and form submit beacons', () => { - document.body.innerHTML = `
`; - document.cookie = `${defaults.name}=${btoa(`{"consent":{"performance":0,"thirdParty":0},"cid":"12345"}`)}`; - const __cb__ = cookieBanner({ - debug: true, - hideBannerOnFormPage: false, - secure: false, - tid: 'UA-141774857-1', - types: { - performance: { - title: 'Performance title', - description: 'Performance description', - labels: { - yes: 'Pages you visit and actions you take will be measured and used to improve the service', - no: 'Pages you visit and actions you take will not be measured and used to improve the service' - }, - fns: [ - () => { } - ] - }, - thirdParty: { - title: 'Third party title', - description: 'Performance description', - labels: { - yes: 'Pages you visit and actions you take will be measured by third parties', - no: 'Pages you visit and actions you take will not be measured by third parties' - }, - fns: [ - () => { } - ] - } - } - }); - const cid = __cb__.getState().persistentMeasurementParams.cid; - - const formDisplayReqURL = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.FORM_DISPLAY - }, 'collect'); - - expect(navigator.sendBeacon).toHaveBeenCalled(); - //1. banner display tracked - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(1, formDisplayReqURL); - - //2. Change preferences and submit form tracked - const fields = Array.from(document.querySelectorAll(`.${defaults.classNames.field}`)); - fields[0].checked = true; - fields[2].checked = true; - document.querySelector(`.${defaults.classNames.submitBtn}`).click(); - - const consentString = composeMeasurementConsent(__cb__.getState().consent); - const preferenceSaveUrl = dataToURL({ - ...composeParams(cid, 'UA-141774857-1'), - ...MEASUREMENTS.SAVE_PREFERENCES, - cd2: consentString === '' ? 'None' : consentString, - cm2: 1, - cm3: 1 - }, 'collect'); - expect(navigator.sendBeacon).toHaveBeenNthCalledWith(2, preferenceSaveUrl); - - }); - -}); \ No newline at end of file diff --git a/packages/cookie-banner/__tests__/measure/validation.js b/packages/cookie-banner/__tests__/measure/validation.js deleted file mode 100644 index ed047b52..00000000 --- a/packages/cookie-banner/__tests__/measure/validation.js +++ /dev/null @@ -1,67 +0,0 @@ -import { dataToURL, composeParams } from '../../src/lib/measurement'; -import { HOSTNAME, MEASUREMENTS } from '../../src/lib/constants'; -// -> https://developers.google.com/analytics/devguides/collection/protocol/v1/validating-hits - -describe(`Cookie banner > measure > hit validation`, () => { - fetch = window.fetch; - it('should send a hit request to Google Analytics for banner display event', async () => { - const reqUrl = dataToURL({ - ...composeParams(12345, 'UA-141774857-1'), - ...MEASUREMENTS.BANNER_DISPLAY, - vp: '1024x768' //override 0x0 from JSDOM - }, '/debug/collect'); - const res = await fetch(reqUrl).then(res => res.json()); - expect(res.hitParsingResult[0].valid).toEqual(true); - expect(res.hitParsingResult[0].hit).toEqual(`${reqUrl.replace(`${HOSTNAME}/`, '')}?_anon_uip=0.0.0.0`); - expect(res.parserMessage[0].description).toEqual('Found 1 hit in the request.'); - }); - - it('should send a hit request to Google Analytics for banner accept event', async () => { - const reqUrl = dataToURL({ - ...composeParams(12345, 'UA-141774857-1'), - ...MEASUREMENTS.BANNER_ACCEPT, - vp: '1024x768' //override 0x0 from JSDOM - }, '/debug/collect'); - const res = await fetch(reqUrl).then(res => res.json()); - expect(res.hitParsingResult[0].valid).toEqual(true); - expect(res.hitParsingResult[0].hit).toEqual(`${reqUrl.replace(`${HOSTNAME}/`, '')}?_anon_uip=0.0.0.0`); - expect(res.parserMessage[0].description).toEqual('Found 1 hit in the request.'); - }); - - it('should send a hit request to Google Analytics for banner options event', async () => { - const reqUrl = dataToURL({ - ...composeParams(12345, 'UA-141774857-1'), - ...MEASUREMENTS.BANNER_OPTIONS, - vp: '1024x768' //override 0x0 from JSDOM - }, '/debug/collect'); - const res = await fetch(reqUrl).then(res => res.json()); - expect(res.hitParsingResult[0].valid).toEqual(true); - expect(res.hitParsingResult[0].hit).toEqual(`${reqUrl.replace(`${HOSTNAME}/`, '')}?_anon_uip=0.0.0.0`); - expect(res.parserMessage[0].description).toEqual('Found 1 hit in the request.'); - }); - - it('should send a hit request to Google Analytics for form display event', async () => { - const reqUrl = dataToURL({ - ...composeParams(12345, 'UA-141774857-1'), - ...MEASUREMENTS.FORM_DISPLAY, - vp: '1024x768' //override 0x0 from JSDOM - }, '/debug/collect'); - const res = await fetch(reqUrl).then(res => res.json()); - expect(res.hitParsingResult[0].valid).toEqual(true); - expect(res.hitParsingResult[0].hit).toEqual(`${reqUrl.replace(`${HOSTNAME}/`, '')}?_anon_uip=0.0.0.0`); - expect(res.parserMessage[0].description).toEqual('Found 1 hit in the request.'); - }); - - it('should send a hit request to Google Analytics for save preferences event', async () => { - const reqUrl = dataToURL({ - ...composeParams(12345, 'UA-141774857-1'), - ...MEASUREMENTS.SAVE_PREFERENCES, - vp: '1024x768' //override 0x0 from JSDOM - }, '/debug/collect'); - const res = await fetch(reqUrl).then(res => res.json()); - expect(res.hitParsingResult[0].valid).toEqual(true); - expect(res.hitParsingResult[0].hit).toEqual(`${reqUrl.replace(`${HOSTNAME}/`, '')}?_anon_uip=0.0.0.0`); - expect(res.parserMessage[0].description).toEqual('Found 1 hit in the request.'); - }); - -}); \ No newline at end of file diff --git a/packages/cookie-banner/__tests__/reject.js b/packages/cookie-banner/__tests__/reject.js index 4be1bc2e..67d1e208 100644 --- a/packages/cookie-banner/__tests__/reject.js +++ b/packages/cookie-banner/__tests__/reject.js @@ -7,7 +7,6 @@ const init = () => { window.__cb__ = cookieBanner({ secure: false, hideBannerOnFormPage: false, - tid: 'UA-141774857-1', types: { test: { title: 'Test title', @@ -41,9 +40,7 @@ describe(`Cookie banner > reject`, () => { it('Should reject all cookies whe the reject button is clicked', async () => { document.querySelector(`.${defaults.classNames.rejectBtn}`).click(); - //get the cid from state - const cid = window.__cb__.getState().persistentMeasurementParams.cid; - expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":0,"performance":0},"cid":"${cid}"}`)}`); + expect(document.cookie).toEqual(`${defaults.name}=${btoa(`{"consent":{"test":0,"performance":0}}`)}`); }); }); diff --git a/packages/cookie-banner/__tests__/state.js b/packages/cookie-banner/__tests__/state.js index 0e18116d..3d3f090a 100644 --- a/packages/cookie-banner/__tests__/state.js +++ b/packages/cookie-banner/__tests__/state.js @@ -11,16 +11,15 @@ describe(`Cookie banner > state > init`, () => { beforeAll(init); it('Should return the Store.getState method on initialisation', async () => { - const Store = cookieBanner({ types: {}, tid: 'UA-XXXXX-Y' }); + const Store = cookieBanner({ types: {} }); expect(Store.getState).not.toBeUndefined(); }); it('Should return the state Object from Store.getState', async () => { - const Store = cookieBanner({ types: {}, tid: 'UA-XXXXX-Y' }); + const Store = cookieBanner({ types: {} }); expect(Store.getState()).toBeDefined(); expect(Store.getState().consent).toEqual({}); - expect(Store.getState().persistentMeasurementParams).toBeDefined(); expect(Store.getState().settings).toBeDefined(); }); @@ -43,7 +42,7 @@ describe(`Cookie banner > state > update/reducers`, () => { ] } }; - const Store = cookieBanner({ tid: 'UA-XXXXX-Y', types }); + const Store = cookieBanner({ types }); expect(Store.getState().settings.types).toEqual(types); }); @@ -74,15 +73,14 @@ describe(`Cookie banner > state > update/reducers`, () => { ] } }; - const state = { settings: {tid: 'UA-XXXXX-Y', types} }; + const state = { settings: { types } }; const data = { test: 1, test2: 0 }; expect(updateConsent(state, data)).toEqual({ consent: { test: 1, test2: 0 }, settings: { - types, - tid: 'UA-XXXXX-Y' + types } }); }); @@ -108,7 +106,7 @@ describe(`Cookie banner > state > update/reducers`, () => { fns: [] } }; - const state = { settings: { types, tid: 'UA-XXXXX-Y' }, consent: { test: 1, test2: 0 } }; + const state = { settings: { types }, consent: { test: 1, test2: 0 } }; const data = { test: { executed: true, @@ -134,7 +132,6 @@ describe(`Cookie banner > state > update/reducers`, () => { expect(updateExecuted(state, data)).toEqual({ consent: { test: 1, test2: 0 }, settings: { - tid: 'UA-XXXXX-Y', types: { test: { executed: true, @@ -159,5 +156,5 @@ describe(`Cookie banner > state > update/reducers`, () => { } } }); - }); + }); }); \ No newline at end of file diff --git a/packages/cookie-banner/__tests__/utils.js b/packages/cookie-banner/__tests__/utils.js index 9acce20e..e31bd7c6 100644 --- a/packages/cookie-banner/__tests__/utils.js +++ b/packages/cookie-banner/__tests__/utils.js @@ -80,10 +80,9 @@ describe('Cookie > Utils > groupValueReducer', () => { describe('Cookie > Utils > extractFromCookie > no cookie', () => { - it('should return default hasCookie, cid, and content properties if no cookie', () => { - const [hasCookie, cid, consent ] = extractFromCookie(defaults); + it('should return default hasCookie and content properties if no cookie', () => { + const [hasCookie, consent ] = extractFromCookie(defaults); expect(hasCookie).toEqual(false); - expect(cid).toBeDefined(); expect(consent).toEqual({}); }); @@ -92,39 +91,36 @@ describe('Cookie > Utils > extractFromCookie > no cookie', () => { describe('Cookie > Utils > extractFromCookie > malformed JSON cookie', () => { - it('should return default hasCookie, cid, and content properties if cookie is not JSON and throws when decoding', () => { + it('should return default hasCookie and content properties if cookie is not JSON and throws when decoding', () => { document.cookie = `${defaults.name}=${btoa(test)}`; - const [hasCookie, cid, consent ] = extractFromCookie(defaults); + const [hasCookie, consent ] = extractFromCookie(defaults); expect(hasCookie).toEqual(false); - expect(cid).toBeDefined(); expect(consent).toEqual({}); }); }); describe('Cookie > Utils > extractFromCookie > well-formed JSON cookie', () => { - it('should return hasCookie, cid, and content properties from well-formed JSON cookie', () => { - document.cookie = `${defaults.name}=${btoa(JSON.stringify({ cid: '12345', consent: { performance: 1, thirdParty: 0 } }))}`; - const [hasCookie, cid, consent ] = extractFromCookie(defaults); + it('should return hasCookie and content properties from well-formed JSON cookie', () => { + document.cookie = `${defaults.name}=${btoa(JSON.stringify({ consent: { performance: 1, thirdParty: 0 } }))}`; + const [hasCookie, consent ] = extractFromCookie(defaults); expect(hasCookie).toEqual(true); - expect(cid).toEqual('12345'); expect(consent).toEqual({ performance: 1, thirdParty: 0 }); }); }); describe('Cookie > Utils > extractFromCookie > cookie not base64 encoded', () => { - it('should return default hasCookie, cid, and content properties if cookie is not base64 encoded and throws when decoding', () => { + it('should return default hasCookie, and content properties if cookie is not base64 encoded and throws when decoding', () => { window.atob = jest.fn(); window.atob.mockImplementation(() => { throw new Error(); }); document.cookie = `${defaults.name}="test"`; - const [hasCookie, cid, consent ] = extractFromCookie(defaults); + const [hasCookie, consent ] = extractFromCookie(defaults); expect(window.atob).toHaveBeenCalled(); expect(hasCookie).toEqual(false); - expect(cid).toBeDefined(); expect(consent).toEqual({}); }); diff --git a/packages/cookie-banner/example/src/js/index.js b/packages/cookie-banner/example/src/js/index.js index c60e32db..f9bacdc8 100644 --- a/packages/cookie-banner/example/src/js/index.js +++ b/packages/cookie-banner/example/src/js/index.js @@ -14,7 +14,6 @@ const writeCookie = state => { const config = { name: '.Components.Dev.Consent', - tid: 'UA-401849-33', secure: false, hideBannerOnFormPage: false, trapTab: true, diff --git a/packages/cookie-banner/measurements.md b/packages/cookie-banner/measurements.md deleted file mode 100644 index fe53902d..00000000 --- a/packages/cookie-banner/measurements.md +++ /dev/null @@ -1,79 +0,0 @@ -# Measurements - -If a Google Analytics tid configuration option is set when initialising the Cookie Banner instance, the following data will be sent to the Analytics account using the Google Measurement API. - - - -## Persistent data -Sent with every measurement - -| Parameter | Value | Definition | -| --------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| v | 1 | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#v | -| tid | from settings | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#tid | -| ds | 'cookiebanner' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ds | -| z | Cachebusting random string, generated by this library | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#z | -| cid | consentId - randomly generated and saved if cookie prefernces are saved | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cid | -| uip | '0.0.0.0' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#uip | -| sr | Screen resolution, detected by this library | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#sr | -| vp | Viewport size, detected by this library | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#vp | -| dh | Hostname, detected by this library | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#dh | -| t | 'event' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#t | -| cd1 | Same as cid parameter | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd | -| cd3 | Hostname (same as dh) | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd | -| cd4 | 'consentAPI' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd4 | - - -## Event data -### Banner displayed to user - -| Parameter | Value | Definition | -| --------- | ---------- | -------------------------------------------------------------------------------------- | -| ec | 'banner' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec | -| ea | 'Displays' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea | -| cm1 | 1 | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm | - - -### User clicks 'accept' on banner - -| Parameter | Value | Definition | -| --------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | -| ec | 'Save Preferences' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec | -| ea | 'Banner' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea | -| cd2 | Comma separated string of all cookie categories, e.g. 'performance,thirdParty' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd | -| cm2 | 1 | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm | -| cm3 | 1 | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm | - - -### User clicks 'options' on banner - -| Parameter | Value | Definition | -| --------- | ------------------ | -------------------------------------------------------------------------------------- | -| ec | 'Banner' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec | -| ea | 'Clicks' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea | -| el | 'Edit Preferences' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#el | -| cm4 | 1 | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm | - - -### Cookie preferences form displays - -| Parameter | Value | Definition | -| --------- | ------------------- | -------------------------------------------------------------------------------------- | -| ec | 'CookiePrefsWidget' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec | -| ea | 'Displays' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea | -| cm5 | 1 | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm | - - -### User saves preferences on cookie preferences form - -| Parameter | Value | Definition | -| --------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | -| ec | 'Save Preferences' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ec | -| ea | 'CookiePrefs' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#ea | -| cd2 | Comma separated string of all cookie categories, e.g. 'performance,thirdParty' | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd | -| cm2 | 1 if performance is true, else 0 [1] | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm | -| cm3 | 1 if thirdPatyr is true, else 0 [1] | https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm | - -[1] Note that the cm2 and cm3 parameters relate to specific cookie categories names - 'performance' and 'third party' cookies respectively. - -Storm defines performance cookies as those that are used to provide anonymous measurements that are used to measure the performance of the web application only. Third party cookies are defined as those that Storm has no control over and includes advertising, marketing, and third party service cookies that are loaded via iframes, embeds, and external libraries. diff --git a/packages/cookie-banner/src/lib/constants.js b/packages/cookie-banner/src/lib/constants.js index f684fa0e..d081a764 100755 --- a/packages/cookie-banner/src/lib/constants.js +++ b/packages/cookie-banner/src/lib/constants.js @@ -3,37 +3,6 @@ export const ACCEPTED_TRIGGERS = ['button', 'a']; export const FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type=hidden])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex="-1"])']; -export const HOSTNAME = 'https://www.google-analytics.com'; - -export const MEASUREMENTS = { - BANNER_DISPLAY: { - ec: 'Banner', - ea: 'Displays', - cm1: 1 - }, - BANNER_ACCEPT: { - ec: 'Save preferences', - ea: 'Banner', - cm2: 1, - cm3: 1 - }, - BANNER_OPTIONS: { - ec: 'Banner', - ea: 'Clicks', - el: 'Edit preferences', - cm4: 1 - }, - FORM_DISPLAY: { - ec: 'CookiePrefsWidget', - ea: 'Displays', - cm5: 1 - }, - SAVE_PREFERENCES: { - ec: 'Save preferences', - ea: 'CookiePrefs' - } -}; - export const EVENTS = { SHOW: 'banner.show', HIDE: 'banner.hide', diff --git a/packages/cookie-banner/src/lib/factory.js b/packages/cookie-banner/src/lib/factory.js index 8322fa24..356ba18f 100644 --- a/packages/cookie-banner/src/lib/factory.js +++ b/packages/cookie-banner/src/lib/factory.js @@ -3,19 +3,16 @@ import { showBanner, initBanner, initForm, initBannerListeners, keyListener } fr import { necessary, apply } from './consent'; import { createStore } from './store'; import { initialState } from './reducers'; -import { composeParams } from './measurement'; export default settings => { /* istanbul ignore next */ if (!cookiesEnabled()) return; const Store = createStore(); - - if (!settings.tid) console.warn('The tid setting is missing. A tid is required for banner measurements.'); //extractFromCookie adds a try/catch guard for cookie reading and JSON.parse in case of cookie name collisions caused by versioning - //for sites that are saving the cookie consent in a different shape, i.e. without cid and consent properties + //for sites that are saving the cookie consent in a different shape, i.e. without consent properties //and for sites with cookies that are not base64 encoded - const [ hasCookie, cid, consent ] = extractFromCookie(settings); + const [ hasCookie, consent ] = extractFromCookie(settings); Store.update( initialState, @@ -23,7 +20,6 @@ export default settings => { settings, bannerOpen: false, keyListener: keyListener(Store), - persistentMeasurementParams: settings.tid ? composeParams(cid, settings.tid) : false, consent, utils: { renderIframe, gtmSnippet } }, diff --git a/packages/cookie-banner/src/lib/measurement.js b/packages/cookie-banner/src/lib/measurement.js deleted file mode 100644 index c4729134..00000000 --- a/packages/cookie-banner/src/lib/measurement.js +++ /dev/null @@ -1,53 +0,0 @@ -import { HOSTNAME } from './constants'; - -export const composeParams = (cid, tid) => ({ - tid, - v: 1, - t: 'event', - ds: 'cookiebanner', - dh: location.hostname, - uip: '0.0.0.0', - sr: window.screen ? `${window.screen.width}x${window.screen.height}`: null, - vp: `${document.documentElement.clientWidth}x${document.documentElement.clientHeight}`, - cid, - cd1: cid, - cd3: location.hostname, - cd4: 'consentAPI' -}); - -export const composeMeasurementConsent = consent => Object.keys(consent).filter(key => consent[key]).join(','); - -export const cacheBuster = () => { - try { - const n = new Uint32Array(1); - window.crypto.getRandomValues(n); - return n[0] & 2147483647; - } catch (err) { - return Math.round(2147483647 * Math.random()); - } -}; - -export const request = url => { - if (navigator.sendBeacon) { - navigator.sendBeacon(url); - return; - } - const img = document.createElement('img'); - img.width = 1; - img.height = 1; - img.src = url; - return img; -}; - -export const composeDataToURL = data => Object.keys(data).reduce((acc, param) => { - if (data[param] !== null) acc.push(`${param}=${encodeURIComponent(data[param])}`); - return acc; -}, []).join('&'); - -export const dataToURL = (data, urlAction) => `${HOSTNAME}/${urlAction}?${composeDataToURL(data)}`; - -export const measure = (state, measurements, urlAction = 'collect') => request(dataToURL({ - ...state.persistentMeasurementParams, - ...measurements, - ...(state.settings.debug ? {} : { z: cacheBuster() }) -}, urlAction)); \ No newline at end of file diff --git a/packages/cookie-banner/src/lib/ui.js b/packages/cookie-banner/src/lib/ui.js index cf2ffeba..b88ae47e 100644 --- a/packages/cookie-banner/src/lib/ui.js +++ b/packages/cookie-banner/src/lib/ui.js @@ -1,15 +1,12 @@ import { writeCookie, groupValueReducer, deleteCookies, getFocusableChildren, broadcast } from './utils'; -import { ACCEPTED_TRIGGERS, MEASUREMENTS, EVENTS } from './constants'; +import { ACCEPTED_TRIGGERS, EVENTS } from './constants'; import { apply } from './consent'; import { updateConsent, updateBannerOpen, updateBanner } from './reducers'; -import { measure, composeMeasurementConsent } from './measurement'; export const initBanner = Store => () => { const state = Store.getState(); if (state.bannerOpen || (state.settings.hideBannerOnFormPage && document.querySelector(`.${state.settings.classNames.formContainer}`))) return; document.body.firstElementChild.insertAdjacentHTML('beforebegin', state.settings.bannerTemplate(state.settings)); - //track banner display - if (state.settings.tid) measure(state, MEASUREMENTS.BANNER_DISPLAY); Store.update(updateBanner, document.querySelector(`.${state.settings.classNames.banner}`)); Store.update(updateBannerOpen, true, [ broadcast(EVENTS.SHOW, Store) ]); @@ -34,7 +31,6 @@ export const initBannerListeners = Store => () => { const acceptBtns = [].slice.call(document.querySelectorAll(composeSelector(state.settings.classNames.acceptBtn))); const rejectBtns = [].slice.call(document.querySelectorAll(composeSelector(state.settings.classNames.rejectBtn))); - const optionsBtn = document.querySelector(composeSelector(state.settings.classNames.optionsBtn)); if (state.settings.trapTab) document.addEventListener('keydown', state.keyListener); @@ -51,16 +47,7 @@ export const initBannerListeners = Store => () => { apply(Store), removeBanner(Store), initForm(Store, false), - broadcast(EVENTS.CONSENT, Store), - //track banner accept click - state => { - if (state.settings.tid) { - measure(state, { - ...MEASUREMENTS.BANNER_ACCEPT, - cd2: composeMeasurementConsent(Store.getState().consent) - }); - } - } + broadcast(EVENTS.CONSENT, Store) ] ); }); @@ -78,27 +65,11 @@ export const initBannerListeners = Store => () => { writeCookie, removeBanner(Store), initForm(Store, false), - broadcast(EVENTS.CONSENT, Store), - state => { - if (state.settings.tid) { - measure(state, { - ...MEASUREMENTS.SAVE_PREFERENCES, - cd2: composeMeasurementConsent(Store.getState().consent) - }); - } - } + broadcast(EVENTS.CONSENT, Store) ] ); }); }); - - //track options click - //shouldn't have to catch and replay the event since we're using beacons - if (optionsBtn && state.settings.tid) { - optionsBtn.addEventListener('click', e => measure(state, MEASUREMENTS.BANNER_OPTIONS)); - } else { - console.warn('No trigger added for options element. Check that the element is a Button or Anchor and that your tid is set.'); - } }; @@ -145,10 +116,6 @@ export const initForm = (Store, track = true) => () => { formContainer.innerHTML = state.settings.formTemplate(suggestedConsent(state)); - //measure form display - //track flag is false if a re-render from a banner acceptance - if (state.settings.tid && track) measure(state, MEASUREMENTS.FORM_DISPLAY); - const form = document.querySelector(`.${state.settings.classNames.form}`); const button = document.querySelector(`.${state.settings.classNames.submitBtn}`); const groups = [].slice.call(document.querySelectorAll(`.${state.settings.classNames.field}`)).reduce((groups, field) => { @@ -186,18 +153,7 @@ export const initForm = (Store, track = true) => () => { removeBanner(Store), broadcast(EVENTS.CONSENT, Store), renderMessage(button), - renderAnnouncement(formAnnouncement), - state => { - if (!state.settings.tid) return; - const consentString = composeMeasurementConsent(state.consent); - const consent = consentString === '' ? 'None' : consentString; - measure(state, { - ...MEASUREMENTS.SAVE_PREFERENCES, - cd2: consent, - cm2: state.consent.performance ? state.consent.performance : 0, - cm3: state.consent.thirdParty ? state.consent.thirdParty : 0 - }); - } + renderAnnouncement(formAnnouncement) ] ); }); diff --git a/packages/cookie-banner/src/lib/utils.js b/packages/cookie-banner/src/lib/utils.js index b71c041e..11857a52 100755 --- a/packages/cookie-banner/src/lib/utils.js +++ b/packages/cookie-banner/src/lib/utils.js @@ -14,7 +14,7 @@ export const cookiesEnabled = () => { export const writeCookie = state => { document.cookie = [ - `${state.settings.name}=${btoa(JSON.stringify({ consent: state.consent, cid: state.persistentMeasurementParams.cid }))};`, + `${state.settings.name}=${btoa(JSON.stringify({ consent: state.consent }))};`, `expires=${(new Date(new Date().getTime() + (state.settings.expiry*24*60*60*1000))).toGMTString()};`, state.settings.path ? `path=${state.settings.path};` : '', state.settings.domain ? `domain=${state.settings.domain};` : '', @@ -53,16 +53,16 @@ export const deleteCookies = state => { .map(updateCookie(state)); }; -//@return array [hasCookie, cid, consent<{}>] +//@return array [hasCookie, consent<{}>] export const extractFromCookie = settings => { try { const cookie = readCookie(settings); - if (!cookie) return [false, uuidv4(), {}]; - const { cid, consent } = JSON.parse(cookie); + if (!cookie) return [false, {}]; + const { consent } = JSON.parse(cookie); const hasCookie = consent !== undefined; - return [hasCookie, cid || uuidv4(), consent || {}]; + return [hasCookie, consent || {}]; } catch (e){ - return [false, uuidv4(), {}]; + return [false, {}]; } }; @@ -108,11 +108,6 @@ export const removeSubdomain = s => { return parts.join('.'); }; -export const uuidv4 = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { - const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); -}); - export const getFocusableChildren = node => [].slice.call(node.querySelectorAll(FOCUSABLE_ELEMENTS.join(','))).filter(el => el.offsetWidth > 0 || el.offsetHeight > 0); export const broadcast = (type, Store) => () => {