├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .jsbeautifyrc ├── .storybook ├── .babelrc ├── config.js └── stories │ └── index.js ├── LICENSE ├── README.md ├── app.yaml ├── build └── template.index.html ├── dist ├── cmp.bundle.js ├── cmp.bundle.js.map ├── index.html ├── ui.chunk.bundle.js ├── ui.chunk.bundle.js.map ├── vendors~ui.chunk.bundle.js └── vendors~ui.chunk.bundle.js.map ├── docs ├── README.md ├── architecture.md ├── bundle-analyser.md ├── cmp-pixel.md ├── cookie.md ├── database.md ├── deploy.md ├── images │ ├── cmp-comps.png │ ├── cmp-comps.svg │ └── control-flow.svg ├── nginx.md ├── resource-links.md └── server-logic.md ├── package-lock.json ├── package.json ├── public ├── assets │ ├── logo-conningbrooks.png │ ├── logo-dewynters.png │ ├── logo-habito.svg │ ├── logo-independent.svg │ ├── logo-miq.png │ └── logo-noma.png ├── cookieCheckFinish.html ├── cookieCheckStart.html └── gtmConsent.js ├── server ├── .env ├── logging │ └── index.js ├── routes │ ├── api.js │ └── cmp.js ├── server.js └── utils │ └── isUserEu.js ├── src ├── api │ └── index.js ├── cmp │ ├── Cmp.js │ ├── customVendors.js │ ├── index.js │ ├── isShowUi.js │ └── tagManager.js ├── configs │ ├── client.0.js │ ├── client.1.js │ ├── client.2.js │ ├── client.3.js │ ├── client.4.js │ ├── client.5.js │ ├── client.6.js │ ├── client.7.js │ ├── client.8.js │ ├── client.9.js │ ├── customVendorList.js │ └── iabVendorList.js ├── loader │ ├── index.js │ └── utils │ │ ├── getClientId.js │ │ ├── isCookie.js │ │ └── isDataLayer.js ├── main.js ├── ui │ ├── App.vue │ ├── components │ │ ├── Breadcrumb.vue │ │ ├── Modal.vue │ │ ├── Purposes.vue │ │ ├── Toggle.vue │ │ └── Vendors.vue │ ├── eventBus.js │ ├── main.js │ ├── store.js │ └── styles │ │ ├── clients │ │ └── client.3.css │ │ ├── style.scss │ │ └── vendors │ │ └── uikit.scss └── utils │ ├── cookies.js │ └── iabListTransforms.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "@babel/env", { 4 | "targets": { 5 | "browsers": ["last 2 versions", "safari >= 8"] 6 | } 7 | }] 8 | ], 9 | "plugins": [ 10 | "syntax-dynamic-import" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig Docs url: https://EditorConfig.org 2 | # No plugin needed when using VSCode 3 | root = true 4 | 5 | # Applies to all files 6 | [*] 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'parserOptions': { 3 | 'allowImportExportEverywhere': true, 4 | 'allowForLoopAfterthoughts': true, 5 | 'parser': 'babel-eslint' 6 | }, 7 | 'extends': [ 8 | 'eslint:recommended', 9 | 'plugin:vue/recommended', 10 | 'airbnb-base' 11 | ], 12 | 'rules': { 13 | 'strict': 0, 14 | 'no-console': 0 // this should be changed for prod... 15 | }, 16 | 'env': { 17 | 'es6': true, 18 | 'browser': true, 19 | 'node': true, 20 | 'shared-node-browser': true 21 | } 22 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # folders 2 | /node_modules/ 3 | /node_modules 4 | dist/ 5 | .idea 6 | 7 | # files 8 | .DS_store 9 | 10 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "editor": { 3 | "formatOnSave": true 4 | }, 5 | "tab_size":2, 6 | "indent_size": 2, 7 | "indent_char": " " 8 | } -------------------------------------------------------------------------------- /.storybook/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-0"] 3 | } -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/vue'; 2 | 3 | import Vue from 'vue'; 4 | import Vuex from 'vuex'; 5 | 6 | import Toggle from '../src/ui/components/Toggle.vue'; 7 | 8 | Vue.use(Vuex); 9 | 10 | Vue.component('toggle', Toggle); 11 | 12 | function loadStories() { 13 | require('./stories/index'); 14 | } 15 | 16 | configure(loadStories, module); -------------------------------------------------------------------------------- /.storybook/stories/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { storiesOf } from '@storybook/vue'; 3 | import Toggle from '../../src/ui/components/Toggle.vue'; 4 | 5 | storiesOf('Toggle', module) 6 | .add('story as a component + template', () => ({ 7 | components: { Toggle }, 8 | template: '' 9 | })) 10 | .add('story as a template', () => ({ 11 | template: '', 12 | })); 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ConsentStack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Consent Stack - Consent Management GDPR 3 |

4 | 5 | # ConsentStack | Consent Management Platform | CMP 6 | **Open source, developer focused & human centric consent management platform.** 7 | 8 | **NOTE:** This repository is a public version of the product available [here](https://consentstack.org/), and previously developed by [OrbisLabs](https://github.com/orbislabs). The code is being prepared for its debut into the open source world, however we welcome any votes of interest, please submit a comment or even just a :+1: on this [issue](https://github.com/ConsentStack/cmp/issues/1). 9 | 10 | ## Motivation 11 | 12 | Whilst exploring the Consent Management Platforms (CMPs) which are available both open and closed source, it was a striking fact that the goal was to drive the highest possible consent / opt-in rates. An [article published by Digiday](https://digiday.com/media/tech-publisher-future-getting-95-percent-audience-consent-ad-tracking/), explained how the publishing group [Future](https://www.futureplc.com/), achieved a 95% opt-in rate through the optimisation and usage of [dark patterns](https://darkpatterns.org/) in their consent modal. 13 | 14 | There was no thought given to the soft squidgy animals at the other end of the screen. Therefore this projects main aims are: 15 | * Educate people on data harvesting on the web. 16 | * Provide fine grained controls, over that data. 17 | * Create a dialog between the user and the website owner. 18 | * Achieve this through a pleasent user experience. 19 | 20 | To help drive the main goals, we have decided to target the developer community to drive adoption of such tooling and use the community to help understand how best to address the web privacy issue. 21 | 22 | ## Privacy Nutrition Labels 23 | 24 | Attribution: [A “Nutrition Label” for Privacy](https://cups.cs.cmu.edu/soups/2009/proceedings/a4-kelley.pdf) by [Lorrie Cranor](http://lorrie.cranor.org/) 25 | 26 | The final goal is to converge on a industry wide nutrition label for privacy. 27 | 28 |

29 | nutrition 30 |

31 | 32 | ## Alternative Options (Open Source) 33 | 34 | Below are a list of some open source variants of the CMP, which exist today - most likely this list will be expanded with further information and moved into its own repository. 35 | 36 | * [IAB Tech Lab Reference Implementation](https://github.com/appnexus/cmp) 37 | * [Axel Springer - OIL.js](https://github.com/as-ideas/oil) 38 | * [Segment.io - Consent Manager](https://github.com/segmentio/consent-manager) 39 | * [Adledger Consortium - ClearGDPR](https://www.cleargdpr.com/) 40 | 41 | ## CMP Demo 42 | 43 | Can be found [here](https://consentstack.org/#/demo). 44 | 45 | ## Installation Instructions 46 | 47 | Clone and install the application: 48 | ```bash 49 | $ git clone https://github.com/ConsentStack/cmp.git 50 | $ cd cmp 51 | $ npm install 52 | ``` 53 | 54 | Build the application files: 55 | ```bash 56 | $ npm run build:prod 57 | ``` 58 | 59 | Start the server, we recommend using a process manager such as [PM2](http://pm2.keymetrics.io/): 60 | ```bash 61 | $ pm2 start server/server.js 62 | ``` 63 | 64 | Including the script tag on your website: 65 | ```javascript 66 | 67 | 68 | 69 | 70 | 71 | ``` 72 | 73 | ## Usage Documentation 74 | 75 | To get a feel for the application, you can find the usage documentation located [here](https://consentstack.org/docs). 76 | 77 | ## Technical Documentation 78 | 79 | Coming soon... 80 | 81 | ## Roadmap 82 | 83 | The below outlines from a very high level the aspirations of this project to make it a solution which will fit any website. Initially this project began as a closed source and cloud hosted solution, I am hoping to lean on the community for direction in design of a generalised, composable consent stack for developers. 84 | 85 | ### Separate Logic from UI 86 | 87 | A primary task for this project would be to separate the various logic for consent framework(s) and the UI element, to allow for developers to build their own UIs using any library or framework, or directly apply into their own website privacy pages. 88 | 89 | The current UI is implemented using code split via `import()` and [Vue.js](https://vuejs.org/), meaning the logic can run and not request the heavy UI files. 90 | 91 | A simple illustration: 92 | ```javascript 93 | import Cmp from 'consentstack-cmp'; 94 | 95 | const cmp = new Cmp(config); 96 | 97 | cmp.setConsent(consentObject); 98 | ``` 99 | 100 | ### UI Themes Library 101 | 102 | To save everyone writing CSS, it would be great to allow the publishing of UI themes into a central place for others to consume. 103 | 104 | ```bash 105 | # install the core library 106 | $ npm install consentstack-cmp 107 | # install the open sourced visual theme 108 | $ npm install consentstack-cmp-theme-dark 109 | ``` 110 | 111 | ### Pluggable Consent Frameworks 112 | 113 | The IAB have created the [Transparency and Consent Framework](http://advertisingconsent.eu/) - which has many flaws: 114 | - Heavily reliant on cookies 115 | - Uses wording hard to decipher for non-techies 116 | - Aims to gather high rates of consent 117 | - etc 118 | 119 | Google is also working on a framework and exposes some APIs into this as are other projects: 120 | - [Open GDPR](https://www.opengdpr.org/) 121 | - [Google Consent](https://support.google.com/admanager/answer/9031024?hl=en&ref_topic=9007190) 122 | 123 | This roadmap item is focused on allow the community to plugin new frameworks to allow full interoperability. 124 | 125 | ### Decentralised Consent Storage 126 | 127 | A singe source of truth for consent receipts could convert this project from an application into a public utility. 128 | 129 | More to come... 130 | 131 | ### Browser Add-On 132 | 133 | Allowing users to set settings to stop annoying popups. 134 | 135 | ## Contributions 136 | 137 | You are welcome to fork the project and submit pull requests to the master branch. More detailed instructions for developers alongside first issues are coming soon! 138 | 139 | ## License 140 | 141 | The ConsentStack CMP is freely distributable under the terms of the [MIT License](https://github.com/ConsentStack/cmp/blob/master/LICENSE). 142 | -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | runtime: nodejs 2 | env: flex 3 | -------------------------------------------------------------------------------- /build/template.index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ConsentStack CMP 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ConsentStack CMP 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Technical Documentation for CMP++ 2 | An IAB compliant CMP, with user privacy preserving features. 3 | 4 | ### Install 5 | 6 | You can install simply by running the following commands: 7 | ```bash 8 | git clone https://github.com/orbislabs/cmp.git 9 | cd cmp 10 | npm install 11 | ``` 12 | 13 | And then run for a development setup: 14 | 15 | ```bash 16 | npm run dev # terminal tab 1 17 | nodemon server.js # terminal tab 2 18 | ``` 19 | 20 | ### The Flow Logic 21 | 22 | ![cmp-flow-logic](../docs/images/control-flow.svg) 23 | 24 | ### Minimal Required API 25 | 26 | ```javascript 27 | showConsentTool() // returns bool 28 | getVendorConsents([vendor_array], callback) // returns permissions for vendor list 29 | getConsentData(null, callback) // returns a base64 encoded cookie value 30 | ping(null, callback) // is cmp loaded? is gdpr global? 31 | ``` 32 | 33 | ### App Architecture 34 | ![app-architecture](../docs/images/cmp-comps.svg) 35 | 36 | ## ConsentString SDK 37 | 38 | Create a new instance of the main class 39 | ```javascript 40 | new ConsentString(baseString) // take input as base64 encoded cookie value, defaults to null 41 | ``` 42 | The class instance offers up the following properties: 43 | 44 | | Name | Type | Argument | Output Type | Output Example | 45 | | ---- | ---- | ------- | ------------ | -------------- | 46 | |`allowedPurposeIds` | property | `null` | `Array.` | [1,2,3,4] | 47 | |`allowedVendorIds` | property | `null` | `Array.` | [1,2,3,4] | 48 | |`cmpId` | property | `null` | `integer` | 7 | 49 | |`cmpVersion` | property | `null` | `integer` | 1 | 50 | |`consentLanguage` | property | `null` | `string` | en | 51 | |`consentScreen` | property | `null` | `integer` | 1 | 52 | |`vendorList` | property | `null` | `object` | {} | 53 | |`vendorListVersion` | property | `null` | `integer` | 7 | 54 | |`version` | property | `null` | `integer` | 1 | 55 | |`getCmpId()` | method | `null` | `integer` | 4 | 56 | |`getCmpVersion()` | method | `null` | `integer` | 1 | 57 | |`getConsentLanguage()` | method | `null` | `string` | en | 58 | |`getConsentScreen()` | method | `null` | `integer` | 5 | 59 | |`getConsentString()` | method | `null` | `string` | BOGHWRWN62525HSGGS | 60 | |`getPurposesAllowed()` | method | `null` | `Array.` | [1,2,3,4] | 61 | |`getVendorListVersion()` | method | `null` | `integer` | 5 | 62 | |`getVendorsAllowed()` | method | `null` |`Array.` | [1,2,3,4] | 63 | |`getVersion()` | method | `null` | `integer` | 5 | 64 | |`isPurposeAllowed(purposeId)` | method | purposeId `integer` | `boolean` | true | 65 | |`isVendorAllowed(vendorId)` | method | vendorId `integer` | `boolean` | true | 66 | |`setCmpId(id)` | method | id `integer` | `null` | | 67 | |`setCmpVersion(version)` | method | version `integer` | `null` | | 68 | |`setConsentLanguage(language)` | method | language `string` | `null` | | 69 | |`setConsentScreen(screenId)` | method | screenId `*` | `null` | | 70 | |`setGlobalVendorList(vendorList)` | method | vendorList `object` | `null` | | 71 | |`setPurposeAllowed(purposeId, value)` | method | purposeId `integer`, value `boolean`| `null` | | 72 | |`setPurposesAllowed(purposeIds)` | method | purposeIds `Array.`| `null` | | 73 | |`setVendorAllowed(vendorId, value)` | method | vendorId `integer`, value `boolean`| `null` | | 74 | |`setVendorsAllowed(vendorIds)` | method | vendorIds `Array.`| `null` | | 75 | 76 | ### Internal APIs 77 | 78 | The following are functions, to be made available internally 79 | ```javascript 80 | readCookie(cookieName = 'euconsent') 81 | writeCookie(cookieName = 'euconsent', value) 82 | queryAllowedVendors([vendorArray]) 83 | updateCmpSettings(configObject, userChoices) 84 | ``` 85 | 86 | ### Config 87 | - **cmpId** = 99 (note: random for now) 88 | - **cmpVersion** = 1 89 | - **consentLanguage** = 'en' 90 | - **consentScreen** = 1 (note: need to figure out what the purpose of this field is) 91 | - **vendorListLocation** = set to a path on the server for now 92 | - **vendorListVersion** = 7 (can be fetched from vendor list itself) 93 | - **iabVersion** = 1.1 (the IAB spec version) 94 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # !Draft! Updated Architecture of CMP !Draft! 2 | 3 | ``` 4 | +-----------+ +-----------+ +-----------+ 5 | | | | | | | 6 | | Loader | ------> | CMP | ------> | UI | 7 | | | | | | | 8 | +-----------+ +-----------+ +-----------+ 9 | 10 | ``` 11 | 12 | ## Loader Module 13 | Responsible for the creation of a data object for consumption by other modules. It is client agnostic, it does not need the client id for any logic. 14 | 15 | #### Flow: 16 | - fetch the clientId from the script tag. 17 | - check if cookies are allowed (1st party) 18 | - check if cookies are allowed (3rd party) 19 | - check for first party cookie presence & get value if exists 20 | - check for third party cookie presence & get value if exists 21 | - send the data to the CMP module for the decision 22 | - log the results in DB 23 | 24 | #### Data Object Generated: 25 | ```javascript 26 | { 27 | clientId : int, 28 | cookie1pAllowed : bool, 29 | cookie3pAllowed : bool, 30 | cookie1pValue : string || null, 31 | cookie3pValue : string || null, 32 | } 33 | ``` 34 | 35 | ### Application Folder Structure 36 | ```bash 37 | /client 38 | /loader 39 | /ui 40 | /api 41 | /cmp 42 | 43 | /server 44 | /routes 45 | /cmp 46 | /log 47 | /api 48 | /cookie 49 | 50 | /lib # code which can be shared across client/server 51 | /cookies 52 | /http 53 | 54 | /build 55 | 56 | /dist 57 | 58 | /docs 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/bundle-analyser.md: -------------------------------------------------------------------------------- 1 | # Webpack Bundle Analyser 2 | 3 | Runs in dev mode only on 127.0.0.1:8888 4 | 5 | Full details: https://github.com/webpack-contrib/webpack-bundle-analyzer 6 | 7 | -------------------------------------------------------------------------------- /docs/cmp-pixel.md: -------------------------------------------------------------------------------- 1 | # Pluto Pixel Documentation 2 | The **cmp-pixel** application is designed to check if a web user has a global IAB consent cookie set, and dependant on its value fire a corresponsing AppNexus pixel or return back a 43byte transparent GIF with no tracking. 3 | 4 | ## Basics 5 | - The application is running on an auto-scaling cluster in Google Cloud. 6 | - SSL is setup for this service. 7 | - It is setup behind a NGINX reverse proxy, and routes requests on `https://pluto.mgr.consensu.org/`. 8 | 9 | ## Endpoints 10 | There are two available endpoints, which the application exposes: 11 | - `/pixel`: this is the production endpoint for usage, which will be described below. 12 | - `/pixel/test`: this endpoint allows for the testing of the internal flow off the application, see below for an example response. 13 | 14 | #### Request: 15 | ```bash 16 | GET /pixel/test?id=12345&segment=12345 17 | ``` 18 | 19 | #### Response: 20 | ```json 21 | { 22 | "isUserEu": true, 23 | "isCookiePresent": "BOPO8xhOPPLRCA0ABBENAk-AAAAXyACABAAGUA", 24 | "isConsentGiven": true, 25 | "fireGif": false, 26 | "firePixel": { 27 | "bool": true, 28 | "id": "https://secure.adnxs.com/px?id=1234&segment=12344&t=2" 29 | } 30 | } 31 | ``` 32 | In the above example, we can see the user made a request from within the EU, has a cookie set, and its value allow for: 33 | - Vendors: 32 & 101 (MiQ & AppNexus) 34 | - Purposes: 1-5 35 | Therefore we fire the pixel shown in the `firePixel.id` key of the object. 36 | 37 | ## Application Flow 38 | The application is designed with some request altering middleware: 39 | ``` 40 | +-----------+ +----------------+ +---------------+ 41 | | | | | | | 42 | | isUserEu | ------> |isCookiePresent | ------> |isConsentGiven | 43 | | | | | | | 44 | +-----------+ +----------------+ +---------------+ 45 | ``` 46 | Once the request body has been appended with the extra fields we check the following logic: 47 | 48 | - Is the user in an EU country & have they given consent - if yes -> fire pixel. 49 | - Is the user not in an EU country - if yes -> fire pixel. 50 | - Any other scenario -> return a GIF. 51 | 52 | ## Usage 53 | 54 | Setup is extremely simple, and follows the same principles as standard pixels. 55 | - Setup by first creating an Appnexus pixel to which you want to redirect on a consent user fire. 56 | - Once that is complete, convert the below template using the values from your Appnexus pixel code. 57 | 58 | ```html 59 | 60 | ``` 61 | 62 | If you have not created a segment into which you want the users automatically populated, you can simply omit that part of the query string: 63 | 64 | ```html 65 | 66 | ``` -------------------------------------------------------------------------------- /docs/cookie.md: -------------------------------------------------------------------------------- 1 | # Pluto Cookies API !!!! DRAFT !!!! 2 | This is a IAB specific module which houses all of the cookies related functions & logic needed for a CMP. This library will cater for 1st/3rd party cookies, including HTTP cookies. 3 | 4 | ### What is needed. 5 | - check if the browser allows setting of 1st party cookies 6 | - check if the browser allows setting of 3rd party cookies 7 | - check if a CMP cookie is present (1st party) 8 | - check if a CMP cookie is present (3rd party) 9 | - write a 1st party cookie 10 | - read a 1st party cookies 11 | - write/request a 3rd party cookie 12 | - read/receive a 3rd party cookie 13 | 14 | ### Reference 15 | https://github.com/InteractiveAdvertisingBureau/Consent-String-SDK-JS 16 | https://github.com/js-cookie/js-cookie 17 | 18 | ### Defaults 19 | ``` 20 | Max-Age= 21 | (30 * 24 * 60 * 60) // equals 30 days 22 | ``` 23 | 24 | ### Cookie Types & Names 25 | #### IAB Cookies 26 | - `euconsent` 27 | - `pubeuconsent` 28 | 29 | #### Pluto Cookies 30 | - `tbd`: custom vendors & purposes 31 | - `tbd`: cookie(s) which aid with GTM integration 32 | 33 | ## API - All promises "thenable" 34 | 35 | ```javascript 36 | Cookies.allowed() // returns bool 37 | ``` 38 | 39 | When we set/get we can check against some definitions / dictionary, i.e name should be x, y, or z. 40 | 41 | ```javascript 42 | Cookies.set('name','value') // 43 | Cookies.get('name') // 44 | 45 | Cookies.setHttp('name','value') 46 | DO NOT IMPLEMENT: Cookies.getHttp() // not sure if we ever need to fetch 3rd party cookies to the UI. 47 | 48 | // this is specific to the IAB spec 49 | // they are availble from the linked lib above. 50 | Cookies.decode() // returns JSON object 51 | Cookies.encode() // returns base64 String 52 | 53 | 54 | 55 | const Cookies = new Cookies(); 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/database.md: -------------------------------------------------------------------------------- 1 | # [Design Doc] DB & Logging 2 | 3 | ## Schema 4 | - event : str (load, view, close) 5 | - geo : str (2 Letter Code) : from NGINX 6 | - ip_trunc : int? : from NGINX 7 | - client_id : int 8 | - 1P_cs : bool 9 | - 3P_cs : bool 10 | - user_agent : str (browser_only) 11 | - cookie_eu_1p : str 12 | - cookie_eu_3p : str 13 | 14 | ## Q's 15 | Currently the app loads on each page view via Google Tag Manager, the issue is that we do not want to send the above data repetitively, so how would we avoid this? Whilst still registering the "load" event. 16 | -------------------------------------------------------------------------------- /docs/deploy.md: -------------------------------------------------------------------------------- 1 | # Deployment Procedure - Google Cloud 2 | 3 | Machine : 1vCPU, 3.75GB, Intel 4 | OS : Ubuntu 16.04 5 | 6 | ## Install Nginx 7 | 8 | Update the package manager: 9 | `$ sudo apt-get update` 10 | 11 | Check ports are clean: 12 | ``` 13 | netstat -an | grep ":80" 14 | netstat | less 15 | lsof -i :80 16 | ``` 17 | 18 | Install Nginx: 19 | `$ sudo apt-get install nginx` 20 | 21 | Check Nginx profiles: 22 | `$ sudo ufw app list` 23 | 24 | Set profile to accept HTTP & HTTPS traffic: 25 | `$ sudo ufw allow "Nginx Full"` 26 | 27 | Check if settings are active: 28 | `$ sudo ufw status` 29 | 30 | Check if Nginx is running: 31 | `$ systemctl status nginx` 32 | 33 | A full check of Nginx, should show a welcome page - this can be done by browsing to the server's external IP: 34 | ``` 35 | $ sudo apt-get install curl 36 | $ curl -4 icanhazip.com 37 | ``` 38 | 39 | #### Now that you have your web server up and running, we can go over some basic management commands. 40 | 41 | To stop your web server, you can type: 42 | `$ sudo systemctl stop nginx` 43 | 44 | To start the web server when it is stopped, type: 45 | `$ sudo systemctl start nginx` 46 | 47 | To stop and then start the service again, type: 48 | `$ sudo systemctl restart nginx` 49 | 50 | If you are simply making configuration changes, Nginx can often reload without dropping connections. To do this, this command can be used: 51 | `$ sudo systemctl reload nginx` 52 | 53 | By default, Nginx is configured to start automatically when the server boots. If this is not what you want, you can disable this behavior by typing: 54 | `$ sudo systemctl disable nginx` 55 | 56 | To re-enable the service to start up at boot, you can type: 57 | `$ sudo systemctl enable nginx` 58 | 59 | ## SSL Setup 60 | 61 | #### NGINX: Generate CSRs (Certificate Signing Requests) 62 | 63 | Run the following command: 64 | `$ openssl req -new -newkey rsa:2048 -nodes -keyout pluto.mgr.consensu.org.key -out pluto.mgr.consensu.org.csr` 65 | 66 | Fill in the additional location, company information and the output will be two files: 67 | `pluto.mgr.consensu.org.csr pluto.mgr.consensu.org.key` 68 | 69 | The CSR file is loaded into the SSL provider dash. 70 | 71 | 72 | ## Nodejs Install 73 | 74 | We will install v10, which is soon going to be the LTS version 75 | 76 | ``` 77 | $ curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - 78 | $ sudo apt-get install -y nodejs 79 | ``` 80 | 81 | Validate the installation: 82 | ``` 83 | $ node -v 84 | $ npm -v 85 | ``` 86 | 87 | Add build-essentials for packages which need to compile code from source: 88 | `$ sudo apt-get install build-essential` 89 | 90 | ## Install PM2 Process Manager 91 | 92 | Now we will install PM2, which is a process manager for Node.js applications. PM2 provides an easy way to manage and daemonize applications (run them in the background as a service). 93 | 94 | `$ sudo npm install -g pm2` 95 | 96 | App can be started as follows: 97 | `$ pm2 start server.js` 98 | 99 | When the app is running, the below command will ensure that the app starts on server boot: 100 | `$ pm2 startup systemd` 101 | 102 | The last line of the resulting output will include a command that you must run with superuser privileges: 103 | ``` 104 | [PM2] Init System found: systemd 105 | [PM2] You have to run this command as root. Execute the following command: 106 | sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/sammy 107 | ``` 108 | 109 | This will create a systemd unit which runs pm2 for your user on boot. This pm2 instance, in turn, runs hello.js. You can check the status of the systemd unit with systemctl: 110 | `$ systemctl status pm2-sammy` 111 | 112 | ## Install Stackdriver Agents on Machine 113 | ```bash 114 | # To install the Stackdriver monitoring agent: 115 | $ curl -sSO https://dl.google.com/cloudagents/install-monitoring-agent.sh 116 | $ sudo bash install-monitoring-agent.sh 117 | 118 | # To install the Stackdriver logging agent: 119 | $ curl -sSO https://dl.google.com/cloudagents/install-logging-agent.sh 120 | $ sudo bash install-logging-agent.sh 121 | ``` 122 | 123 | -------------------------------------------------------------------------------- /docs/images/cmp-comps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConsentStack/cmp/0eca47167a3823fc9063f2fe54806a73517234d9/docs/images/cmp-comps.png -------------------------------------------------------------------------------- /docs/images/cmp-comps.svg: -------------------------------------------------------------------------------- 1 | 2 |
Class : Cmp
Class : Cmp
Class : Store
Class : Store
Class : Config
Class : Config
utils.js
utils.js
vendorlist.js
vendorlist.js
cookie.js
cookie.js
log.js
log.js
main.js
main.js
ExpressServer()
ExpressServer()
cmp.bundle.js
cmp.bundle.js
-------------------------------------------------------------------------------- /docs/images/control-flow.svg: -------------------------------------------------------------------------------- 1 | 2 |
Load CMP
Load CMP
Check if cookies are blocked
Check if cookies are blocked
Expose "no permissions" in API
Expose "no permissions" in API
Check if IAB cookie is set
Check if IAB cookie is set
Show consent modal
Show consent modal
Load cookie data into __cmp API
Load cookie data into __cmp API
Save settings into IAB cookie
Save settings into IAB cookie
Expose settings in __cmp API
Expose settings in __cmp API
YES
YES
NO
NO
Legend
Legend
Expose settings in __cmp API
Expose settings in __cmp API
-------------------------------------------------------------------------------- /docs/nginx.md: -------------------------------------------------------------------------------- 1 | # NGINX Configuration 2 | This file currently contains the config for the auto scaling cluster, which means it also has the configuration and setup details for: 3 | - cmp-pixel 4 | 5 | **There is no guarantee that these files are up to date** 6 | 7 | #### /etc/nginx/nginx.conf 8 | ```bash 9 | user www-data; 10 | worker_processes auto; 11 | pid /run/nginx.pid; 12 | 13 | events { 14 | worker_connections 768; 15 | # multi_accept on; 16 | } 17 | 18 | http { 19 | 20 | ## 21 | # Basic Settings 22 | ## 23 | 24 | sendfile on; 25 | tcp_nopush on; 26 | tcp_nodelay on; 27 | keepalive_timeout 65; 28 | types_hash_max_size 2048; 29 | # server_tokens off; 30 | 31 | # server_names_hash_bucket_size 64; 32 | # server_name_in_redirect off; 33 | 34 | geoip_country /usr/share/GeoIP/GeoIP.dat; 35 | 36 | include /etc/nginx/mime.types; 37 | default_type application/octet-stream; 38 | 39 | ## 40 | # SSL Settings 41 | ## 42 | 43 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE 44 | ssl_prefer_server_ciphers on; 45 | 46 | ## 47 | # Logging Settings 48 | ## 49 | 50 | access_log /var/log/nginx/access.log; 51 | error_log /var/log/nginx/error.log; 52 | 53 | ## 54 | # Gzip Settings 55 | ## 56 | 57 | gzip on; 58 | gzip_disable "msie6"; 59 | 60 | # gzip_vary on; 61 | # gzip_proxied any; 62 | # gzip_comp_level 6; 63 | # gzip_buffers 16 8k; 64 | # gzip_http_version 1.1; 65 | # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 66 | 67 | ## 68 | # Virtual Host Configs 69 | ## 70 | 71 | include /etc/nginx/conf.d/*.conf; 72 | include /etc/nginx/sites-enabled/*; 73 | } 74 | ``` 75 | 76 | 77 | #### /etc/nginx/sites-enabled/pluto.mgr.consensu.org 78 | 79 | ```bash 80 | # Default server configuration 81 | # 82 | server { 83 | listen 80 default_server; 84 | listen [::]:80 default_server; 85 | 86 | # SSL configuration 87 | # 88 | listen 443 ssl; 89 | 90 | server_name pluto.mgr.consensu.org; 91 | ssl_certificate /etc/nginx/ssl/ssl-bundle.crt; 92 | ssl_certificate_key /etc/nginx/ssl/pluto.mgr.consensu.org.key; 93 | # listen 443 ssl default_server; 94 | # listen [::]:443 ssl default_server; 95 | # 96 | # Note: You should disable gzip for SSL traffic. 97 | # See: https://bugs.debian.org/773332 98 | # 99 | # Read up on ssl_ciphers to ensure a secure configuration. 100 | # See: https://bugs.debian.org/765782 101 | # 102 | # Self signed certs generated by the ssl-cert package 103 | # Don't use them in a production server! 104 | # 105 | # include snippets/snakeoil.conf; 106 | 107 | # root /var/www/html; 108 | 109 | # Add index.php to the list if you are using PHP 110 | # index index.html index.htm index.nginx-debian.html; 111 | 112 | location / { 113 | proxy_pass http://localhost:5000/; 114 | proxy_redirect http://localhost:5000/ https://pluto.mgr.consensu.org; 115 | proxy_http_version 1.1; 116 | proxy_set_header X-Request-Country $geoip_country_code; 117 | proxy_set_header Upgrade $http_upgrade; 118 | proxy_set_header Connection 'upgrade'; 119 | proxy_set_header Host $host; 120 | proxy_set_header X-Cookie-Euconsent $cookie_euconsent; 121 | proxy_cache_bypass $http_upgrade; 122 | #proxy_cookie_domain localhost .consensu.org; 123 | } 124 | 125 | ##### 126 | # Consent Pixel application config lives here 127 | ##### 128 | 129 | location /pixel { 130 | proxy_pass http://localhost:5001/; 131 | proxy_redirect http://localhost:5000/ https://pluto.mgr.consensu.org; 132 | proxy_http_version 1.1; 133 | proxy_set_header X-Request-Country $geoip_country_code; 134 | proxy_set_header Upgrade $http_upgrade; 135 | proxy_set_header Connection 'upgrade'; 136 | proxy_set_header Host $host; 137 | proxy_set_header X-Cookie-Euconsent $cookie_euconsent; 138 | proxy_cache_bypass $http_upgrade; 139 | #proxy_cookie_domain localhost .consensu.org; 140 | } 141 | } 142 | 143 | -------------------------------------------------------------------------------- /docs/resource-links.md: -------------------------------------------------------------------------------- 1 | # Resource & Links 2 | 3 | http://acdn.origin.appnexus.net/cmp/docs/#/tools/vendor-cookie-inspector -------------------------------------------------------------------------------- /docs/server-logic.md: -------------------------------------------------------------------------------- 1 | # Design Doc for Server Logic 2 | 3 | The server currently is minimal, mainly serving static bundles. 4 | 5 | ### Information Needed 6 | - Country of the Request 7 | - Cookie value of the Request 8 | - Client ID of the Request 9 | 10 | ### Current Headers 11 | - `X-Request-Country` : which is a 2 letter country code, list is available here ... 12 | 13 | ### Added soon 14 | - `Cookie` : which will contain `custom` & `euconsent` cookies 15 | 16 | ### Route 17 | - We should move the `clientId` into the request route, this will make it easier to parse on the route for various logic 18 | 19 | ### Logic 20 | Could look as below: 21 | - check the clientId, as each one will/may have individual logic 22 | - check the geo of the user, as for non-EU users we may just send a `HTTP 444` 23 | - check the cookie value, because if a user had granted all needed consent, again we can avoid showing the UI - we do need to send the app however as it exposes a global client side API for other scripts to query 24 | 25 | ### TODO: 26 | #### P1 27 | - pass cookie header to server 28 | - move clientId into querystring 29 | - implement local nginx for proper testing 30 | 31 | #### P2 32 | - setup DB for client config -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmp", 3 | "version": "0.3.0", 4 | "description": "Consent Management Platform", 5 | "main": "./server/server.js", 6 | "scripts": { 7 | "start": "node ./server/server.js", 8 | "develop": "NODE_ENV=development ASSET_PATH=/ webpack --mode development --watch", 9 | "build:dev": "NODE_ENV=development ASSET_PATH=/ webpack --mode development", 10 | "build:staging": "NODE_ENV=development ASSET_PATH=https://staging-dot-pluto-cmp.appspot.com/ webpack --mode development", 11 | "build:prod": "NODE_ENV=production ASSET_PATH=https://pluto.mgr.consensu.org/ webpack --mode production", 12 | "deploy": "NODE_ENV=production pm2 start ./server/server.js", 13 | "storybook": "start-storybook -p 9001 -c .storybook" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/orbislabs/cmp.git" 18 | }, 19 | "keywords": [ 20 | "consent", 21 | "management", 22 | "cmp" 23 | ], 24 | "author": "Dennis Yurkevich", 25 | "license": "ISC", 26 | "bugs": { 27 | "url": "https://github.com/orbislabs/cmp/issues" 28 | }, 29 | "homepage": "https://github.com/orbislabs/cmp#readme", 30 | "engines": { 31 | "node": "8.x" 32 | }, 33 | "dependencies": { 34 | "@google-cloud/logging-winston": "^0.9.0", 35 | "@vuikit/icons": "^0.8.1", 36 | "@vuikit/theme": "^0.8.0", 37 | "babel-polyfill": "^6.26.0", 38 | "consent-string": "^1.2.4", 39 | "cookie-parser": "^1.4.3", 40 | "dotenv": "^6.0.0", 41 | "express": "^4.16.3", 42 | "js-cookie": "^2.2.0", 43 | "uikit": "^3.0.0-beta.42", 44 | "vue": "^2.5.16", 45 | "vuex": "^3.0.1", 46 | "vuikit": "^0.8.4", 47 | "winston": "2.4.3" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.0.0-beta.51", 51 | "@babel/plugin-transform-runtime": "^7.0.0-beta.51", 52 | "@babel/preset-env": "^7.0.0-beta.51", 53 | "@storybook/vue": "^4.0.0-alpha.14", 54 | "babel-eslint": "^8.2.3", 55 | "babel-loader": "^8.0.0-beta.2", 56 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 57 | "babel-preset-env": "^1.7.0", 58 | "clean-webpack-plugin": "^0.1.19", 59 | "css-loader": "^0.28.11", 60 | "eslint": "^4.19.1", 61 | "eslint-config-airbnb-base": "^12.1.0", 62 | "eslint-plugin-import": "^2.12.0", 63 | "eslint-plugin-vue": "^4.5.0", 64 | "html-loader": "^0.5.5", 65 | "html-webpack-exclude-assets-plugin": "0.0.7", 66 | "html-webpack-plugin": "^3.2.0", 67 | "mini-css-extract-plugin": "^0.4.0", 68 | "node-sass": "^4.9.0", 69 | "sass-loader": "^7.0.3", 70 | "sass-resources-loader": "^1.3.3", 71 | "style-loader": "^0.21.0", 72 | "uglifyjs-webpack-plugin": "^1.2.7", 73 | "vue-loader": "^15.2.4", 74 | "vue-style-loader": "^4.1.0", 75 | "vue-template-compiler": "^2.5.16", 76 | "webpack": "4.9.2", 77 | "webpack-bundle-analyzer": "^2.13.1", 78 | "webpack-cli": "^3.1.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /public/assets/logo-conningbrooks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConsentStack/cmp/0eca47167a3823fc9063f2fe54806a73517234d9/public/assets/logo-conningbrooks.png -------------------------------------------------------------------------------- /public/assets/logo-dewynters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConsentStack/cmp/0eca47167a3823fc9063f2fe54806a73517234d9/public/assets/logo-dewynters.png -------------------------------------------------------------------------------- /public/assets/logo-independent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 12 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/assets/logo-miq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConsentStack/cmp/0eca47167a3823fc9063f2fe54806a73517234d9/public/assets/logo-miq.png -------------------------------------------------------------------------------- /public/assets/logo-noma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConsentStack/cmp/0eca47167a3823fc9063f2fe54806a73517234d9/public/assets/logo-noma.png -------------------------------------------------------------------------------- /public/cookieCheckFinish.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /public/cookieCheckStart.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /public/gtmConsent.js: -------------------------------------------------------------------------------- 1 | function getCookie(sKey) { 2 | if (!sKey) { 3 | return null; 4 | } 5 | return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null; 6 | } 7 | 8 | if (typeof window.dataLayer === 'undefined') { 9 | console.log('[INFO][Module-TMS] dataLayer is undefined.'); 10 | } else if (getCookie('isFunctionalAllowed') || getCookie('isAnalyticsAllowed') || getCookie('isMarketingAllowed')) { 11 | console.log('[INFO][Module-TMS] Consent settings already set.'); 12 | } else { 13 | console.log('[INFO][Module-TMS] Non-EU User, setting full consent.'); 14 | document.cookie = 'isFunctionalAllowed=true'; 15 | document.cookie = 'isAnalyticsAllowed=true'; 16 | document.cookie = 'isMarketingAllowed=true'; 17 | window.dataLayer.push({ 18 | event: 'updatedConsentSettings', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /server/.env: -------------------------------------------------------------------------------- 1 | # production config 2 | URL_PRODUCTION=https://pluto.mgr.consensu.org 3 | PORT_PRODUCTION=5000 4 | 5 | # staging config 6 | URL_STAGING=http://staging.pluto.mgr.consensu.org 7 | PORT_STAGING=6000 8 | # development config 9 | URL_DEVELOPMENT=localhost 10 | PORT_DEVELOPMENT=5000 11 | 12 | # general config 13 | GLOBAL_VENDOR_LIST=https://vendorlist.consensu.org/vendorlist.json -------------------------------------------------------------------------------- /server/logging/index.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'); 2 | const { LoggingWinston } = require('@google-cloud/logging-winston'); 3 | const appVersion = require('../../package.json').version; 4 | 5 | const Logger = winston.Logger; 6 | const Console = winston.transports.Console; 7 | 8 | if (process.env.NODE_ENV === 'development') { 9 | process.env.GOOGLE_APPLICATION_CREDENTIALS = '/Users/dennisy/keys/pluto-cmp-f1d01876b199.json'; 10 | } 11 | 12 | const loggingWinston = new LoggingWinston({ 13 | labels: { 14 | app: 'cmp', 15 | env: process.env.NODE_ENV, 16 | version: appVersion, 17 | }, 18 | }); 19 | 20 | const logger = new Logger({ 21 | level: 'info', // log at 'info' and above 22 | transports: [ 23 | new Console(), 24 | loggingWinston, 25 | ], 26 | }); 27 | 28 | module.exports = (req, res, next) => { 29 | logger.info({ 30 | httpRequest: { 31 | status: res.statusCode, 32 | requestUrl: req.originalUrl, 33 | requestMethod: req.method, 34 | userAgent: req.get('User-Agent'), 35 | referer: req.get('Referer'), 36 | protocol: req.protocol, 37 | }, 38 | countryCode: req.get('X-AppEngine-Country'), 39 | // TODO: gdrpApplies: isUserEu(), 40 | cookieData: { 41 | euconsentBool: (req.cookies.euconsent !== undefined), 42 | euconsent: req.cookies.euconsent, 43 | eupubconsent: req.cookies.eupubconsent, 44 | custom: req.cookies.custom, 45 | }, 46 | }); 47 | next(); 48 | }; 49 | -------------------------------------------------------------------------------- /server/routes/api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const router = express.Router(); 4 | 5 | // TODO: decide on the middleware for logging 6 | router.get('/getCookie', (req, res) => { 7 | res.set({ 8 | 'Access-Control-Allow-Origin': req.get('Origin'), 9 | 'Access-Control-Allow-Credentials': 'true', 10 | }); 11 | res.json({ 12 | euconsent: (req.cookies.euconsent) ? req.cookies.euconsent : false, 13 | plutoconsent: (req.cookies.plutoconsent) ? req.cookies.plutoconsent : false, 14 | }); 15 | res.end(); 16 | }); 17 | 18 | router.get('/setCookie', (req, res) => { 19 | res.set({ 20 | 'Access-Control-Allow-Origin': req.get('Origin'), 21 | 'Access-Control-Allow-Credentials': 'true', 22 | }); 23 | res.cookie(req.query.n, req.query.c, { maxAge: 336960000, domain: '.consensu.org' }); 24 | res.end(); 25 | }); 26 | 27 | module.exports = router; 28 | -------------------------------------------------------------------------------- /server/routes/cmp.js: -------------------------------------------------------------------------------- 1 | // setup express routing 2 | const express = require('express'); 3 | const path = require('path'); 4 | 5 | const router = express.Router(); 6 | 7 | const rootPath = path.join(__dirname, '../../dist'); 8 | const publicPath = path.join(__dirname, '../../public'); 9 | 10 | // using this to geo is user is in EU 11 | const geo = require('../utils/isUserEu'); 12 | const logger = require('../logging/index'); 13 | 14 | router.use(logger); 15 | 16 | router.get('/', (req, res) => { 17 | if (req.hostname !== 'localhost' && req.get('X-AppEngine-Country')) { 18 | if (geo.isUserEu(req.get('X-AppEngine-Country'))) { 19 | res.sendFile('cmp.bundle.js', { 20 | root: rootPath, 21 | }); 22 | } else { 23 | res.sendFile('gtmConsent.js', { 24 | root: publicPath, 25 | }); 26 | } 27 | } else { 28 | res.sendFile('cmp.bundle.js', { 29 | root: rootPath, 30 | }); 31 | } 32 | }); 33 | 34 | router.get('/demo', (req, res) => { 35 | res.sendFile('cmp.bundle.js', { 36 | root: rootPath, 37 | }); 38 | }); 39 | 40 | router.get('/dev/:id*?', (req, res) => { 41 | const clientId = req.params.id || 0; 42 | res.send(``); 43 | }); 44 | 45 | module.exports = router; 46 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 3 | 4 | const express = require('express'); 5 | const cookieParser = require('cookie-parser'); 6 | const cmp = require('./routes/cmp'); 7 | const api = require('./routes/api'); 8 | 9 | const PORT = process.env.PORT || 8080; 10 | 11 | const app = express(); 12 | 13 | app.use(cookieParser()); 14 | app.use('/cmp', cmp); 15 | app.use('/api', api); 16 | app.use(express.static('dist')); 17 | app.use(express.static('public')); 18 | 19 | // fire up the server 20 | app.listen(PORT, () => { 21 | console.log(`CMP++ :: ExpressServer --> Mode : ${process.env.NODE_ENV}`); 22 | console.log(`CMP++ :: ExpressServer --> NodeV : ${process.version}`); // TODO: node v9.8.0 -> needs to be updated 23 | console.log(`CMP++ :: ExpressServer --> Port : ${PORT}`); 24 | }); 25 | -------------------------------------------------------------------------------- /server/utils/isUserEu.js: -------------------------------------------------------------------------------- 1 | // TODO: this can be converted to a middleware function, 2 | // which can be called once - to add in req.body 3 | 4 | const euCountries = [ 5 | 'GB', 6 | 'AT', 7 | 'BE', 8 | 'BG', 9 | 'HR', 10 | 'CY', 11 | 'CZ', 12 | 'DK', 13 | 'EE', 14 | 'FI', 15 | 'FR', 16 | 'DE', 17 | 'GR', 18 | 'HU', 19 | 'IE', 20 | 'IT', 21 | 'LV', 22 | 'LT', 23 | 'LU', 24 | 'MT', 25 | 'NL', 26 | 'PL', 27 | 'PT', 28 | 'RO', 29 | 'SK', 30 | 'SI', 31 | 'ES', 32 | 'SE', 33 | ]; 34 | 35 | function isUserEu(countryCode) { 36 | return euCountries.includes(countryCode); 37 | } 38 | 39 | module.exports.isUserEu = isUserEu; 40 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | /* eslint no-underscore-dangle: "off" */ 2 | export default function initApi(cmp) { 3 | function api(command, parameter = null, callback = null) { 4 | switch (command) { 5 | case 'ping': 6 | cmp.ping(parameter, callback); 7 | break; 8 | case 'getVendorConsents': 9 | cmp.getVendorConsents(parameter, callback); 10 | break; 11 | case 'getConsentData': 12 | cmp.getConsentData(parameter, callback); 13 | break; 14 | case 'showConsentTool': 15 | cmp.showConsentTool(parameter, callback); 16 | break; 17 | default: 18 | console.error('CMP => Error: unknown command'); 19 | } 20 | } 21 | window.__cmp = api; 22 | console.log('[INFO][Module][API]: ', api.prototype); 23 | return Promise.resolve(true); 24 | } 25 | -------------------------------------------------------------------------------- /src/cmp/Cmp.js: -------------------------------------------------------------------------------- 1 | import { ConsentString } from 'consent-string'; 2 | import getCustomVendorsAllowed from './customVendors'; 3 | import * as cookies from '../utils/cookies'; 4 | import iabVendorList from '../configs/iabVendorList'; 5 | import tagManagerModule from './tagManager'; 6 | 7 | export default class Cmp extends ConsentString { 8 | constructor(result = null) { 9 | super(result.iabCookie); 10 | this.setCmpId(52); 11 | this.setCmpVersion(1); 12 | this.setConsentLanguage('en'); 13 | this.setConsentScreen(1); 14 | this.setGlobalVendorList(iabVendorList); 15 | this.clientId = result.clientId; 16 | this.cmpLoaded = false; 17 | this.customVendorsAllowed = getCustomVendorsAllowed(); 18 | } 19 | 20 | readyCmpAPI() { 21 | this.cmpLoaded = true; 22 | console.log(`[INFO][Module-CMP]: CMP loaded status is => ${this.cmpLoaded}`); 23 | Promise.resolve(); 24 | } 25 | 26 | ping(empty = null, callback = () => {}) { 27 | const result = { 28 | gdprAppliesGlobally: true, 29 | cmpLoaded: this.cmpLoaded, 30 | }; 31 | callback(result, true); 32 | } 33 | 34 | // TODO: allVendors does not exist right now 35 | getVendorConsents(vendors = allVendors, callback = () => {}) { 36 | const result = {}; 37 | vendors.forEach((element) => { 38 | result[element] = this.isVendorAllowed(element); 39 | }); 40 | callback(result, true); 41 | } 42 | 43 | getConsentData(empty = null, callback = () => {}) { 44 | const result = this.getConsentString(); 45 | callback(result, true); 46 | } 47 | 48 | showConsentTool() { 49 | console.log('[INFO][CMP-Module] showConsentTool() has been called.'); 50 | return new Promise((resolve, reject) => { 51 | return import( 52 | /* webpackMode: "lazy", 53 | webpackPrefetch: true, 54 | webpackChunkName: "ui" */ 55 | '../ui/main') 56 | .then(appModule => appModule.default(this.clientId)) 57 | .then(userConsentObject => this.updateCmpAndWriteCookie(userConsentObject)) 58 | .then(() => tagManagerModule()) 59 | .then(() => cookies.requestHttpCookies('euconsent', this.getConsentString())) 60 | .then(result => Promise.resolve(result)) 61 | .catch(err => console.error(err)); 62 | }); 63 | } 64 | 65 | setCustomVendorsAllowed(customVendorArray) { 66 | this.customVendorsAllowed = customVendorArray; 67 | } 68 | 69 | updateCmpAndWriteCookie(consentObject) { 70 | return new Promise((resolve, reject) => { 71 | this.setPurposesAllowed(consentObject.purposes); 72 | this.setVendorsAllowed(consentObject.vendors); 73 | this.setCustomVendorsAllowed(consentObject.customVendors); 74 | console.log('[INFO][Module-CMP]: Received consent object', consentObject); 75 | console.log(`CMP => Set CustomVendors:${JSON.stringify(this.customVendorsAllowed)}`); 76 | console.log(`CMP => Set Purposes: ${JSON.stringify(this.getPurposesAllowed())}`); 77 | console.log(`CMP => Set Vendors: ${JSON.stringify(this.getVendorsAllowed())}`); 78 | cookies.writeCookieCustom(JSON.stringify(consentObject.customVendors)); 79 | cookies.writeCookie(this.getConsentString()) 80 | .then((result) => { 81 | if (result) resolve(true); 82 | }); 83 | }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/cmp/customVendors.js: -------------------------------------------------------------------------------- 1 | import * as cookies from '../utils/cookies'; 2 | 3 | export default function getCustomVendorsAllowed() { 4 | if (typeof cookies.readCookieSync('custom') === 'string') { 5 | return JSON.parse(cookies.readCookieSync('custom')); 6 | } 7 | return []; 8 | } 9 | -------------------------------------------------------------------------------- /src/cmp/index.js: -------------------------------------------------------------------------------- 1 | import Cmp from './Cmp'; 2 | 3 | export default function initCmp(loaderData) { 4 | console.log('[INFO][Module-Loader]: ', loaderData); 5 | return new Promise((resolve, reject) => { 6 | const cmp = new Cmp(loaderData); 7 | if (!cmp) reject(); 8 | console.log('[INFO][Module-CMP]: ', cmp); 9 | resolve(cmp); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /src/cmp/isShowUi.js: -------------------------------------------------------------------------------- 1 | export default function (cookie) { 2 | const isCookiePresent = (typeof cookie === 'string'); 3 | console.log('[INFO][Module-isShowUi]: ', !isCookiePresent); 4 | return Promise.resolve(!isCookiePresent); 5 | } 6 | -------------------------------------------------------------------------------- /src/cmp/tagManager.js: -------------------------------------------------------------------------------- 1 | import Cookie from 'js-cookie'; 2 | 3 | const map = new Map(); 4 | map.set(11, 'isFunctionalAllowed'); 5 | map.set(12, 'isAnalyticsAllowed'); 6 | map.set(13, 'isMarketingAllowed'); 7 | 8 | function updateConsentCookies(purposeArray) { 9 | for (let i = 0; i < purposeArray.length; i++) { 10 | if (map.get(purposeArray[i])) { 11 | Cookie.set(map.get(purposeArray[i]), true); 12 | } 13 | } 14 | } 15 | 16 | export default function tagManagerModule() { 17 | return new Promise((resolve, reject) => { 18 | const purposeArray = cmp.getPurposesAllowed(); 19 | if (purposeArray === undefined || purposeArray.length == 0) { 20 | console.log('[INFO][Module-TMS] => No user consented purposes'); 21 | resolve(false); 22 | } else if (!window.dataLayer) { 23 | console.log('[INFO][Module-TMS] => No dataLayer Detected'); 24 | resolve(false); 25 | } else { 26 | console.log('[INFO][Module-TMS] => Consented purposes', purposeArray); 27 | updateConsentCookies(purposeArray); 28 | window.dataLayer.push({ event: 'updatedConsentSettings' }); 29 | console.log('[INFO][Module-TMS] => Updated dataLayer', window.dataLayer); 30 | resolve(true); 31 | } 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /src/configs/client.0.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 0, 3 | clientName: 'demo', 4 | clientLogo: '', 5 | defaults: { 6 | purposes: [1, 2, 3, 4, 5], 7 | vendors: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 8 | customVendors: [], 9 | }, 10 | views: { 11 | homeView: { 12 | title: 'We use cookies...', 13 | body: `And you should be able to take control over your personal data. Therefore we are providing you with new controls to manage your data, to give you a better internet experience. 14 | If you click Accept all below you consent to us and all the third-parties mentioned in our Privacy and Cookie Notice setting cookies and processing your personal data for the purposes of analytics and advertising.`, 15 | }, 16 | purposeView: { 17 | title: 'Our Purposes', 18 | body: `We use cookies and work with various partners to create a tailored experience for our users. 19 | Below you can find all the purposes for which we collect your device data.`, 20 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 21 | purposeText: [{ 22 | id: 1, 23 | purpose: 'Storage and access of information', 24 | }, 25 | { 26 | id: 2, 27 | purpose: 'Personalisation', 28 | }, 29 | { 30 | id: 3, 31 | purpose: 'Ad selection, delivery, reporting', 32 | }, 33 | { 34 | id: 4, 35 | purpose: 'Measurement', 36 | }, 37 | { 38 | id: 5, 39 | purpose: 'Content selection, delivery, reporting', 40 | }, 41 | ], 42 | }, 43 | vendorView: { 44 | title: 'Our Partners', 45 | body: `Below are a list of our partners, please explore each vendors policy and remove any 46 | which do not conform to your privacy standards.`, 47 | }, 48 | }, 49 | }; 50 | 51 | export default clientConfig; 52 | -------------------------------------------------------------------------------- /src/configs/client.1.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 1, 3 | clientName: 'MiQ', 4 | clientLogo: __webpack_public_path__ + '/assets/logo-miq.png', 5 | clientStyle: false, 6 | defaults: { 7 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 8 | vendors: [32, 101], 9 | customVendors: [1001, 1002, 1003, 1004], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'We are MiQ!', 14 | body: `MiQ and our partners use technology such as cookies on our site for analytics and advertising purposes. 15 | By clicking 'I Agree' you consent to use of this technology across the web by us and the third-parties mentioned in our Privacy Policy. 16 | You can change your mind and revisit your consent choices at anytime by returning to this site.`, 17 | }, 18 | purposeView: { 19 | purposeType: 'default', 20 | title: 'Our Purposes', 21 | body: `We use cookies and work with various partners to create a tailored experience for our users. 22 | Below you can find all the purposes for which we collect data from your device.`, 23 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 24 | purposeText: [{ 25 | id: 1, 26 | purpose: 'Storage and access of information', 27 | }, 28 | { 29 | id: 2, 30 | purpose: 'Personalisation', 31 | }, 32 | { 33 | id: 3, 34 | purpose: 'Ad selection, delivery, reporting', 35 | }, 36 | { 37 | id: 4, 38 | purpose: 'Measurement', 39 | }, 40 | { 41 | id: 5, 42 | purpose: 'Content selection, delivery, reporting', 43 | }, 44 | ], 45 | }, 46 | vendorView: { 47 | title: 'Our Partners', 48 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 49 | which do not conform to your privacy standards.`, 50 | }, 51 | }, 52 | }; 53 | 54 | export default clientConfig; 55 | -------------------------------------------------------------------------------- /src/configs/client.2.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 2, 3 | clientName: 'The Independent', 4 | clientLogo: __webpack_public_path__ + '/assets/logo-independent.svg', 5 | defaults: { 6 | purposes: [1, 2, 3, 4, 5], 7 | vendors: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 8 | customVendors: [], 9 | }, 10 | views: { 11 | homeView: { 12 | title: 'Dear Reader,', 13 | body: `In order to run a successful online newspaper, The Independent and third parties are storing and accessing information on your device with cookies and other technologies. 14 | Several third parties are also processing personal data to show you personalised content and ads. 15 | This can also be on websites that are not just ours. Under new EU Regulation, your consent is needed for both setting cookies and processing your personal data. 16 | You can view our privacy policy here.`, 17 | }, 18 | purposeView: { 19 | title: 'Our Purposes', 20 | body: `We use cookies and work with various partners to create a tailored experience for our users. 21 | Below you can find all the purposes for which we collect data from your device.`, 22 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 23 | purposeText: [{ 24 | id: 1, 25 | purpose: 'Storage and access of information', 26 | }, 27 | { 28 | id: 2, 29 | purpose: 'Personalisation', 30 | }, 31 | { 32 | id: 3, 33 | purpose: 'Ad selection, delivery, reporting', 34 | }, 35 | { 36 | id: 4, 37 | purpose: 'Measurement', 38 | }, 39 | { 40 | id: 5, 41 | purpose: 'Content selection, delivery, reporting', 42 | }, 43 | ], 44 | }, 45 | vendorView: { 46 | title: 'Our Partners', 47 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 48 | which do not conform to your privacy standards.`, 49 | }, 50 | }, 51 | }; 52 | 53 | export default clientConfig; 54 | -------------------------------------------------------------------------------- /src/configs/client.3.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 3, 3 | clientName: 'Habito', 4 | clientLogo: __webpack_public_path__ + '/assets/logo-habito.svg', 5 | clientStyle: true, 6 | defaults: { 7 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 8 | vendors: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 9 | customVendors: [], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'We value your privacy.', 14 | body: `You have control over your personal data. 15 | We use technology to create a personalised experience for our users, for analytics and for advertising. 16 | Please click "I agree" to provide consent to us and third parties mentioned in our Privacy & Cookie Policy to process your data for these purposes. 17 | You can change your settings at any time.`, 18 | }, 19 | purposeView: { 20 | purposeType: 'default', 21 | title: 'Our Purposes', 22 | body: `We use cookies and work with various partners to create a tailored experience for our users. 23 | Below you can find all the purposes for which we collect data from your device.`, 24 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 25 | purposeText: [{ 26 | id: 1, 27 | purpose: 'Storage and access of information', 28 | }, 29 | { 30 | id: 2, 31 | purpose: 'Personalisation', 32 | }, 33 | { 34 | id: 3, 35 | purpose: 'Ad selection, delivery, reporting', 36 | }, 37 | { 38 | id: 4, 39 | purpose: 'Measurement', 40 | }, 41 | { 42 | id: 5, 43 | purpose: 'Content selection, delivery, reporting', 44 | }, 45 | ], 46 | }, 47 | vendorView: { 48 | title: 'Our Partners', 49 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 50 | which do not conform to your privacy standards.`, 51 | }, 52 | }, 53 | }; 54 | 55 | export default clientConfig; 56 | -------------------------------------------------------------------------------- /src/configs/client.4.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 4, 3 | clientName: 'Custom Purpose Client', 4 | clientLogo: 'https://upload.wikimedia.org/wikipedia/fi/a/ab/The_Independent_logo.svg', 5 | defaults: { 6 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 7 | vendors: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 8 | // customPurposes: [11, 12, 13], 9 | customVendors: [], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'Dear Reader,', 14 | body: `In order to run a successful online newspaper, The Independent and third parties are storing and accessing information on your device with cookies and other technologies. 15 | Several third parties are also processing personal data to show you personalised content and ads. 16 | This can also be on websites that are not just ours. Under new EU Regulation, your consent is needed for both setting cookies and processing your personal data. 17 | You can view our privacy policy here.`, 18 | }, 19 | purposeView: { 20 | purposeType: 'default', 21 | title: 'Our Purposes', 22 | body: `We use cookies and work with various partners to create a tailored experience for our users. 23 | Below you can find all the purposes for which we collect data from your device.`, 24 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 25 | purposeText: [{ 26 | id: 1, 27 | purpose: 'Storage and access of information', 28 | }, 29 | { 30 | id: 2, 31 | purpose: 'Personalisation', 32 | }, 33 | { 34 | id: 3, 35 | purpose: 'Ad selection, delivery, reporting', 36 | }, 37 | { 38 | id: 4, 39 | purpose: 'Measurement', 40 | }, 41 | { 42 | id: 5, 43 | purpose: 'Content selection, delivery, reporting', 44 | }, 45 | ], 46 | }, 47 | vendorView: { 48 | title: 'Our Partners', 49 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 50 | which do not conform to your privacy standards.`, 51 | }, 52 | }, 53 | }; 54 | 55 | export default clientConfig; 56 | -------------------------------------------------------------------------------- /src/configs/client.5.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 5, 3 | clientName: 'Noma', 4 | clientLogo: __webpack_public_path__ + 'assets/logo-noma.png', 5 | clientStyle: false, 6 | defaults: { 7 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 8 | vendors: [101], 9 | customVendors: [1001, 1003], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'We value your privacy.', 14 | body: `We and our partners use cookies on our site for analytics and advertising purposes. 15 | By selecting "I agree" you are consenting to use of this technology across the web by us and our ad partners. 16 | More information can be found in our privacy policy. 17 | If you change your mind, you can change your preferences at anytime when returning to this site.`, 18 | }, 19 | purposeView: { 20 | purposeType: 'default', 21 | title: 'Our Purposes', 22 | body: `We use cookies and work with various partners to create a tailored experience for our users. 23 | Below you can find all the purposes for which we collect data from your device.`, 24 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 25 | purposeText: [{ 26 | id: 1, 27 | purpose: 'Storage and access of information', 28 | }, 29 | { 30 | id: 2, 31 | purpose: 'Personalisation', 32 | }, 33 | { 34 | id: 3, 35 | purpose: 'Ad selection, delivery, reporting', 36 | }, 37 | { 38 | id: 4, 39 | purpose: 'Measurement', 40 | }, 41 | { 42 | id: 5, 43 | purpose: 'Content selection, delivery, reporting', 44 | }, 45 | ], 46 | }, 47 | vendorView: { 48 | title: 'Our Partners', 49 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 50 | which do not conform to your privacy standards.`, 51 | }, 52 | }, 53 | }; 54 | 55 | export default clientConfig; 56 | -------------------------------------------------------------------------------- /src/configs/client.6.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 6, 3 | clientName: 'Conningbrook Lakes', 4 | clientLogo: __webpack_public_path__ + 'assets/logo-conningbrooks.png', 5 | clientStyle: false, 6 | defaults: { 7 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 8 | vendors: [101], 9 | customVendors: [1001, 1003], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'We value your privacy.', 14 | body: `We and our partners use cookies on our site for analytics and advertising purposes. 15 | By selecting "I agree" you are consenting to use of this technology across the web by us and our ad partners. 16 | More information can be found in our privacy policy. 17 | If you change your mind, you can change your preferences at anytime when returning to this site.`, 18 | }, 19 | purposeView: { 20 | purposeType: 'default', 21 | title: 'Our Purposes', 22 | body: `We use cookies and work with various partners to create a tailored experience for our users. 23 | Below you can find all the purposes for which we collect data from your device.`, 24 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 25 | purposeText: [{ 26 | id: 1, 27 | purpose: 'Storage and access of information', 28 | }, 29 | { 30 | id: 2, 31 | purpose: 'Personalisation', 32 | }, 33 | { 34 | id: 3, 35 | purpose: 'Ad selection, delivery, reporting', 36 | }, 37 | { 38 | id: 4, 39 | purpose: 'Measurement', 40 | }, 41 | { 42 | id: 5, 43 | purpose: 'Content selection, delivery, reporting', 44 | }, 45 | ], 46 | }, 47 | vendorView: { 48 | title: 'Our Partners', 49 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 50 | which do not conform to your privacy standards.`, 51 | }, 52 | }, 53 | }; 54 | 55 | export default clientConfig; 56 | -------------------------------------------------------------------------------- /src/configs/client.7.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 7, 3 | clientName: 'Dewynters', 4 | clientLogo: __webpack_public_path__ + 'assets/logo-dewynters.png', 5 | clientStyle: false, 6 | defaults: { 7 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 8 | vendors: [101], 9 | customVendors: [1001, 1003], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'We value your privacy.', 14 | body: `We and our partners use cookies on our site for analytics and advertising purposes. 15 | By selecting "I agree" you are consenting to use of this technology across the web by us and our ad partners. 16 | More information can be found in our privacy policy. 17 | If you change your mind, you can change your preferences at anytime when returning to this site.`, 18 | }, 19 | purposeView: { 20 | purposeType: 'default', 21 | title: 'Our Purposes', 22 | body: `We use cookies and work with various partners to create a tailored experience for our users. 23 | Below you can find all the purposes for which we collect data from your device.`, 24 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 25 | purposeText: [{ 26 | id: 1, 27 | purpose: 'Storage and access of information', 28 | }, 29 | { 30 | id: 2, 31 | purpose: 'Personalisation', 32 | }, 33 | { 34 | id: 3, 35 | purpose: 'Ad selection, delivery, reporting', 36 | }, 37 | { 38 | id: 4, 39 | purpose: 'Measurement', 40 | }, 41 | { 42 | id: 5, 43 | purpose: 'Content selection, delivery, reporting', 44 | }, 45 | ], 46 | }, 47 | vendorView: { 48 | title: 'Our Partners', 49 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 50 | which do not conform to your privacy standards.`, 51 | }, 52 | }, 53 | }; 54 | 55 | export default clientConfig; 56 | -------------------------------------------------------------------------------- /src/configs/client.8.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 8, 3 | clientName: 'Dewynters - The Bodyguard', 4 | clientLogo: __webpack_public_path__ + 'assets/logo-dewynters.png', 5 | clientStyle: false, 6 | defaults: { 7 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 8 | vendors: [101], 9 | customVendors: [1001, 1003], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'We value your privacy.', 14 | body: `We and our partners use cookies on our site for analytics and advertising purposes. 15 | By selecting "I agree" you are consenting to use of this technology across the web by us and our ad partners. 16 | More information can be found in our privacy policy. 17 | If you change your mind, you can change your preferences at anytime when returning to this site.`, 18 | }, 19 | purposeView: { 20 | purposeType: 'default', 21 | title: 'Our Purposes', 22 | body: `We use cookies and work with various partners to create a tailored experience for our users. 23 | Below you can find all the purposes for which we collect data from your device.`, 24 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 25 | purposeText: [{ 26 | id: 1, 27 | purpose: 'Storage and access of information', 28 | }, 29 | { 30 | id: 2, 31 | purpose: 'Personalisation', 32 | }, 33 | { 34 | id: 3, 35 | purpose: 'Ad selection, delivery, reporting', 36 | }, 37 | { 38 | id: 4, 39 | purpose: 'Measurement', 40 | }, 41 | { 42 | id: 5, 43 | purpose: 'Content selection, delivery, reporting', 44 | }, 45 | ], 46 | }, 47 | vendorView: { 48 | title: 'Our Partners', 49 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 50 | which do not conform to your privacy standards.`, 51 | }, 52 | }, 53 | }; 54 | 55 | export default clientConfig; 56 | -------------------------------------------------------------------------------- /src/configs/client.9.js: -------------------------------------------------------------------------------- 1 | const clientConfig = { 2 | clientId: 9, 3 | clientName: 'Dewynters - Bat Out Of Hell', 4 | clientLogo: __webpack_public_path__ + 'assets/logo-dewynters.png', 5 | clientStyle: false, 6 | defaults: { 7 | purposes: [1, 2, 3, 4, 5, 11, 12, 13], 8 | vendors: [101], 9 | customVendors: [1001, 1003], 10 | }, 11 | views: { 12 | homeView: { 13 | title: 'We value your privacy.', 14 | body: `We and our partners use cookies on our site for analytics and advertising purposes. 15 | By selecting "I agree" you are consenting to use of this technology across the web by us and our ad partners. 16 | More information can be found in our privacy policy. 17 | If you change your mind, you can change your preferences at anytime when returning to this site.`, 18 | }, 19 | purposeView: { 20 | purposeType: 'default', 21 | title: 'Our Purposes', 22 | body: `We use cookies and work with various partners to create a tailored experience for our users. 23 | Below you can find all the purposes for which we collect data from your device.`, 24 | vendorsText: 'You can also view and remove the partners and vendors with whom we share your information: ', 25 | purposeText: [{ 26 | id: 1, 27 | purpose: 'Storage and access of information', 28 | }, 29 | { 30 | id: 2, 31 | purpose: 'Personalisation', 32 | }, 33 | { 34 | id: 3, 35 | purpose: 'Ad selection, delivery, reporting', 36 | }, 37 | { 38 | id: 4, 39 | purpose: 'Measurement', 40 | }, 41 | { 42 | id: 5, 43 | purpose: 'Content selection, delivery, reporting', 44 | }, 45 | ], 46 | }, 47 | vendorView: { 48 | title: 'Our Partners', 49 | body: `Below is a list of our technology partners, please explore each partners policy and remove any 50 | which do not conform to your privacy standards.`, 51 | }, 52 | }, 53 | }; 54 | 55 | export default clientConfig; 56 | -------------------------------------------------------------------------------- /src/configs/customVendorList.js: -------------------------------------------------------------------------------- 1 | const customVendorList = { 2 | "vendorListVersion": 1, 3 | "lastUpdated": "2018-05-31T16:43:19Z", 4 | "vendors": [{ 5 | "id": 1001, 6 | "name": "Google Analytics", 7 | "policyUrl": "https://policies.google.com/privacy", 8 | "purposeIds": [], 9 | "legIntPurposeIds": [], 10 | "featureIds": [] 11 | }, 12 | { 13 | "id": 1002, 14 | "name": "Google Tag Manager", 15 | "policyUrl": "https://policies.google.com/privacy", 16 | "purposeIds": [], 17 | "legIntPurposeIds": [], 18 | "featureIds": [] 19 | }, 20 | { 21 | "id": 1003, 22 | "name": "DoubleClick", 23 | "policyUrl": "https://policies.google.com/technologies/ads", 24 | "purposeIds": [], 25 | "legIntPurposeIds": [], 26 | "featureIds": [] 27 | }, 28 | { 29 | "id": 1004, 30 | "name": "Facebook", 31 | "policyUrl": "https://www.facebook.com/full_data_use_policy", 32 | "purposeIds": [], 33 | "legIntPurposeIds": [], 34 | "featureIds": [] 35 | }, 36 | { 37 | "id": 1999, 38 | "name": "PLACEHOLDER", 39 | "policyUrl": "PLACEHOLDER", 40 | "purposeIds": [], 41 | "legIntPurposeIds": [], 42 | "featureIds": [] 43 | } 44 | ], 45 | "purposes": [{ 46 | "id": 11, 47 | "name": "Functional", 48 | "description": "These cookies are required to enable core site functionality.", 49 | "disabled": true 50 | }, { 51 | "id": 12, 52 | "name": "Analytics", 53 | "description": "These cookies allow us to analyse the site usage so we can measure and improve performance." 54 | }, { 55 | "id": 13, 56 | "name": "Marketing", 57 | "description": "These cookies are used by our advertising partners to serve you ads relevant to your interests." 58 | }], 59 | }; 60 | 61 | export default customVendorList; 62 | -------------------------------------------------------------------------------- /src/loader/index.js: -------------------------------------------------------------------------------- 1 | import getClientId from './utils/getClientId'; 2 | import isDataLayer from './utils/isDataLayer'; 3 | import packageJson from '../../package.json'; 4 | import { 5 | is1PCSupported, 6 | is3PCSupported, 7 | get1PCookieValue, 8 | } from './utils/isCookie'; 9 | 10 | export default function initLoader() { 11 | return Promise.all([ 12 | getClientId(), 13 | isDataLayer(), 14 | is1PCSupported(), 15 | is3PCSupported(), 16 | get1PCookieValue('euconsent'), // cookie.get('1P','name'); 17 | ]).then((result) => { 18 | // return {clientId, isDataLayer, is1PCSupported, is3PCSupported, iabCookie} = ...result 19 | return { 20 | appVersion: packageJson.version, // TODO: fetch this fro 21 | clientId: result[0], 22 | isDataLayer: result[1], 23 | is1PCSupported: result[2], 24 | is3PCSupported: result[3], 25 | iabCookie: result[4], 26 | }; 27 | }).catch(err => console.log(err)); 28 | } 29 | -------------------------------------------------------------------------------- /src/loader/utils/getClientId.js: -------------------------------------------------------------------------------- 1 | export default function getClientID() { 2 | return new Promise((resolve, reject) => { 3 | const scriptElement = document.getElementById('pluto-cmp-js-src'); 4 | const clientId = (scriptElement) ? scriptElement.getAttribute('client-id') : 0; 5 | resolve(parseInt(clientId)); 6 | }); 7 | } -------------------------------------------------------------------------------- /src/loader/utils/isCookie.js: -------------------------------------------------------------------------------- 1 | function is1PCSupported() { 2 | return new Promise((resolve, reject) => { 3 | let cookieEnabled = (navigator.cookieEnabled) ? true : false; 4 | if (typeof navigator.cookieEnabled == "undefined" && !cookieEnabled) { 5 | document.cookie = "testcookie"; 6 | cookieEnabled = (document.cookie.indexOf("testcookie") != -1) ? true : false; 7 | } 8 | if (cookieEnabled == true || false) { 9 | resolve(cookieEnabled); 10 | } else { 11 | reject(); 12 | } 13 | }); 14 | } 15 | 16 | // TODO: is3PCSupported() this takes quite a long time to resolve, and blocks loading of CMP + API 17 | function is3PCSupported() { 18 | return new Promise((resolve, reject) => { 19 | const frame = document.createElement('iframe'); 20 | frame.setAttribute('src', __webpack_public_path__ + 'cookieCheckStart.html'); 21 | frame.setAttribute('style', 'display:none'); 22 | document.body.appendChild(frame); 23 | const receiveMessage = function (evt) { 24 | if (evt.data === 'MM:3PCunsupported') { 25 | resolve(false); 26 | } else if (evt.data === 'MM:3PCsupported') { 27 | resolve(true); 28 | } 29 | }; 30 | window.addEventListener('message', receiveMessage, false); 31 | }); 32 | } 33 | 34 | function get1PCookieValue(name = 'euconsent') { 35 | const value = '; ' + document.cookie; 36 | const parts = value.split('; ' + name + '='); 37 | if (parts.length === 2) { 38 | return Promise.resolve(parts.pop().split(';').shift()); 39 | } 40 | return Promise.resolve(false); 41 | } 42 | 43 | export { 44 | is1PCSupported, 45 | is3PCSupported, 46 | get1PCookieValue, 47 | }; 48 | -------------------------------------------------------------------------------- /src/loader/utils/isDataLayer.js: -------------------------------------------------------------------------------- 1 | // renameBug.. 2 | export default function isDataLayer() { 3 | return new Promise((resolve, reject) => { 4 | const result = (typeof dataLayer !== 'undefined') ? true : false; 5 | resolve(result); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import initLoader from './loader'; 2 | import initCmp from './cmp'; 3 | import initApi from './api'; 4 | import isShowUi from './cmp/isShowUi'; 5 | import tagManagerModule from './cmp/tagManager'; 6 | 7 | async function init() { 8 | const loaderData = await initLoader(); 9 | const cmp = await initCmp(loaderData); 10 | window.cmp = cmp; // TODO: remove this it should not be set globally 11 | initApi(cmp) 12 | .then(() => isShowUi(loaderData.iabCookie)) 13 | .then((bool) => { 14 | if (bool) cmp.showConsentTool(); 15 | Promise.resolve(true); 16 | }) 17 | .then(result => cmp.readyCmpAPI(result)) 18 | .then(() => tagManagerModule()) 19 | .catch(err => console.error(err)); 20 | } 21 | init(); 22 | -------------------------------------------------------------------------------- /src/ui/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 45 | 46 | 51 | -------------------------------------------------------------------------------- /src/ui/components/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /src/ui/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 46 | 47 | 55 | -------------------------------------------------------------------------------- /src/ui/components/Purposes.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 78 | 79 | 84 | -------------------------------------------------------------------------------- /src/ui/components/Toggle.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 231 | 232 | 282 | -------------------------------------------------------------------------------- /src/ui/components/Vendors.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 87 | 88 | 90 | -------------------------------------------------------------------------------- /src/ui/eventBus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | export default new Vue(); 4 | -------------------------------------------------------------------------------- /src/ui/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuikit from 'vuikit'; 3 | import { ElementModalClose } from 'vuikit/lib/modal'; 4 | import EventBus from './eventBus'; 5 | 6 | // import and make global all components 7 | import App from './App.vue'; 8 | import Modal from './components/Modal.vue'; 9 | import Purposes from './components/Purposes.vue'; 10 | import Vendors from './components/Vendors.vue'; 11 | import Breadcrumb from './components/Breadcrumb.vue'; 12 | import Toggle from './components/Toggle.vue'; 13 | 14 | Vue.use(Vuikit); 15 | 16 | // registering all components globally 17 | Vue.component('CmpApp', App); 18 | Vue.component('Modal', Modal); 19 | Vue.component('Purposes', Purposes); 20 | Vue.component('Vendors', Vendors); 21 | Vue.component('AppBreadcrumb', Breadcrumb); 22 | Vue.component('CmpToggle', Toggle); 23 | Vue.component('ElementModalClose', ElementModalClose); 24 | 25 | Vue.mixin({ 26 | methods: { 27 | $ready(fn) { 28 | if (process.env.NODE_ENV === 'production') { 29 | return this.$nextTick(fn); 30 | } 31 | 32 | setTimeout(() => { 33 | this.$nextTick(fn); 34 | }); 35 | }, 36 | }, 37 | }); 38 | 39 | // creating a root in the DOM for the app to attach to, when called 40 | const divToAttachApp = document.createElement('div'); 41 | divToAttachApp.setAttribute('id', 'cmp-app'); 42 | document.body.appendChild(divToAttachApp); 43 | 44 | // create the app instance and attach it to the DOM in a hidden state 45 | const vm = new Vue(App).$mount('#cmp-app'); 46 | 47 | // this function is called to load the UI, it accepts the clientId 48 | function renderVueApp(clientId) { 49 | return new Promise((resolve, reject) => { 50 | if (vm) { 51 | vm.$store.commit('setClientId', parseInt(clientId, 10)); 52 | vm.$store.dispatch('setClientId', parseInt(clientId, 10)); 53 | vm.$store.commit('changeShowState', true); 54 | EventBus.$on('save-selection', (value) => { 55 | console.log('[INFO][CMP-UI] Resolving Promise (save-selection):', value); 56 | resolve(value); 57 | }); 58 | } else { 59 | console.error('CMP-UI :: No App Present'); 60 | reject(); 61 | } 62 | }); 63 | } 64 | 65 | export default renderVueApp; 66 | -------------------------------------------------------------------------------- /src/ui/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import createLogger from 'vuex/dist/logger'; 4 | import EventBus from './eventBus'; 5 | 6 | import iabVendorList from '../configs/iabVendorList'; 7 | import customVendorList from '../configs/customVendorList'; 8 | 9 | Vue.use(Vuex); 10 | 11 | const debug = process.env.NODE_ENV !== 'production'; 12 | 13 | export default new Vuex.Store({ 14 | // strict: true, // TODO: this should bet set in dev ONLY! 15 | plugins: debug ? [createLogger()] : [], 16 | 17 | state: { 18 | isShow: false, 19 | currentView: 'Modal', 20 | clientId: null, 21 | vendorsList: iabVendorList.vendors.concat((customVendorList.vendors || []).map((vendor) => { 22 | return { 23 | ...vendor, 24 | isCustom: true, 25 | }; 26 | })), 27 | userConsentObject: { 28 | purposes: [], 29 | vendors: [], 30 | customVendors: [], 31 | // customPurposes: [], 32 | }, 33 | clientConfig: null, 34 | }, 35 | 36 | getters: { 37 | getUserConsentObject: state => state.userConsentObject, 38 | getCurrentClientConfig: state => state.clientConfig, 39 | getCurrentClientVendorList: (state, getters) => { 40 | // first we fetch the IAB, and filter the IAB vendors 41 | if (!getters.getCurrentClientConfig) { 42 | return []; 43 | } 44 | const { 45 | vendors, 46 | customVendors, 47 | } = getters.getCurrentClientConfig.defaults; 48 | const configVendors = [...vendors, ...customVendors]; 49 | return state.vendorsList.filter(vendor => configVendors.includes(vendor.id)); 50 | }, 51 | }, 52 | 53 | mutations: { 54 | setClientId(state, clientId) { 55 | state.clientId = clientId; 56 | }, 57 | setClientConfig(state, clientConfig) { 58 | state.clientConfig = clientConfig; 59 | }, 60 | updateUserConsentObject(state, payload) { 61 | const { 62 | toggleType, 63 | toggleValue, 64 | toggleId, 65 | } = payload; 66 | if (!['purposes', 'vendors'].includes(toggleType)) { 67 | console.log('CMP-UI :: Unknown Toggle Type', toggleType); 68 | return; 69 | } 70 | const attr = toggleType === 'purposes' ? 'purposes' : toggleId <= 1000 ? 'vendors' : 'customVendors'; 71 | let arrayValue = state.userConsentObject[attr]; 72 | 73 | if (attr === 'purposes' && toggleId === 13) { 74 | if (toggleValue) { 75 | arrayValue.push(1, 2, 3, 4, 5); 76 | } else { 77 | arrayValue = arrayValue.filter(a => a > 5); 78 | } 79 | } 80 | 81 | if (toggleValue) { 82 | if (!arrayValue.includes(toggleId)) { 83 | arrayValue.push(toggleId); 84 | } 85 | } else { 86 | arrayValue = arrayValue.filter(id => id !== toggleId); 87 | } 88 | state.userConsentObject = { 89 | ...state.userConsentObject, 90 | [attr]: arrayValue, 91 | }; 92 | }, 93 | // this mutation is called right after setting the clientId, so we can use the getter 94 | // to fetch the correct client config object 95 | syncClientDefaultsToUserObject(state, payload) { 96 | state.userConsentObject = { 97 | purposes: [...payload.purposes], 98 | vendors: [...payload.vendors], 99 | customVendors: [...payload.customVendors], 100 | // customPurposes: [...payload.customPurposes], 101 | }; 102 | }, 103 | 104 | changeShowState(state, payload) { 105 | state.isShow = payload; 106 | }, 107 | 108 | changeCurrentView(state, payload) { 109 | state.currentView = payload; 110 | }, 111 | 112 | }, 113 | actions: { 114 | setFullConsent({ 115 | commit, 116 | getters, 117 | }, payload) { 118 | console.log(`CMP-UI :: full-consent Event: ${payload}`); 119 | const defaultConfig = getters.getCurrentClientConfig.defaults; 120 | EventBus.$emit('save-selection', defaultConfig); 121 | commit('syncClientDefaultsToUserObject', defaultConfig); 122 | commit('changeShowState', false); 123 | commit('changeCurrentView', 'Modal'); 124 | }, 125 | setPartialConsent({ 126 | commit, 127 | getters, 128 | }, payload) { 129 | console.log(`CMP-UI :: partial-consent Event: ${payload}`); 130 | const config = getters.getUserConsentObject; 131 | EventBus.$emit('save-selection', config); 132 | commit('changeShowState', false); 133 | commit('changeCurrentView', 'Modal'); 134 | }, 135 | setClientId({ 136 | commit, 137 | }, clientId) { 138 | return import( 139 | /* webpackMode : "eager" */ 140 | `../configs/client.${clientId}.js`).then((configImport) => { 141 | const config = configImport.default; 142 | commit('setClientConfig', config); 143 | commit('syncClientDefaultsToUserObject', config.defaults); 144 | }).catch(err => console.error(err)); 145 | }, 146 | }, 147 | }); 148 | -------------------------------------------------------------------------------- /src/ui/styles/clients/client.3.css: -------------------------------------------------------------------------------- 1 | div.uk-modal-body { 2 | background-color: #3D3C3A; 3 | } 4 | 5 | h1.client-styles { 6 | font-family: Replica-Regular, sans-serif; 7 | background-color: #3D3C3A; 8 | color: #FFF; 9 | } 10 | 11 | h4.client-styles { 12 | font-family: Replica-Regular, sans-serif; 13 | font-weight: 600; 14 | background-color: #3D3C3A; 15 | color: #FFF; 16 | } 17 | 18 | p.client-styles { 19 | font-family: Replica-Regular, sans-serif; 20 | background-color: #3D3C3A; 21 | color: #FFF; 22 | } 23 | 24 | button.client-styles.uk-button-default { 25 | color: #FFF !important; 26 | background-color: #3D3C3A !important; 27 | } 28 | 29 | button.client-styles.uk-button-secondary { 30 | color: black !important; 31 | background-color: #FFF !important; 32 | } 33 | 34 | span.client-styles { 35 | color: #FFF !important; 36 | } 37 | 38 | table.client-styles td { 39 | color: #FFF !important; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/ui/styles/style.scss: -------------------------------------------------------------------------------- 1 | @import './vendors/uikit.scss'; 2 | 3 | * { 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 5 | } 6 | 7 | li, td { 8 | color: #666; 9 | } 10 | 11 | p { 12 | /* 1 */ 13 | font-size: 16px; 14 | font-weight: normal; 15 | line-height: 1.5; 16 | /* 2 */ 17 | -webkit-text-size-adjust: 100%; 18 | /* 3 */ 19 | background: #fff; 20 | color: #666; 21 | } 22 | 23 | @media only screen and (max-width : 767px) { 24 | .uk-button { 25 | padding: 0px 15px; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/cookies.js: -------------------------------------------------------------------------------- 1 | const host = (window && window.location && window.location.hostname) || ''; 2 | const parts = host.split('.'); 3 | 4 | const COOKIE_DOMAIN = parts.length > 1 ? `;domain=.${parts.slice(-2).join('.')}` : ''; 5 | const COOKIE_MAX_AGE = 33696000; 6 | const COOKIE_NAME = 'euconsent'; 7 | const PATH = '/'; 8 | 9 | function checkCookiesEnabled() { 10 | let cookieEnabled = (navigator.cookieEnabled) ? true : false; 11 | if (typeof navigator.cookieEnabled == 'undefined' && !cookieEnabled) { 12 | document.cookie = 'testcookie'; 13 | cookieEnabled = (document.cookie.indexOf('testcookie') != -1) ? true : false; 14 | } 15 | return cookieEnabled; 16 | } 17 | 18 | 19 | // this now lives in loader.js - and can be removed from here 20 | function checkCookiesEnabledPromise() { 21 | return new Promise((resolve, reject) => { 22 | let cookieEnabled = (navigator.cookieEnabled) ? true : false; 23 | if (typeof navigator.cookieEnabled == 'undefined' && !cookieEnabled) { 24 | document.cookie = 'testcookie'; 25 | cookieEnabled = (document.cookie.indexOf('testcookie') != -1) ? true : false; 26 | } 27 | if (cookieEnabled == true || false) { 28 | console.log(`CMP => set cookie status : ${cookieEnabled}`); 29 | resolve(cookieEnabled); 30 | } else { 31 | reject(new Error('Error checking if CMP can set cookies')); 32 | } 33 | }); 34 | } 35 | 36 | function checkIabCookie(result) { 37 | if (result == false) console.error('CMP => Cookies are blocked!'); 38 | return new Promise((resolve, reject) => { 39 | readCookie('euconsent') 40 | .then((result) => { 41 | if (result) { 42 | console.log(`CMP => IAB cookie loaded: ${result}`); 43 | resolve(result); 44 | } else { 45 | console.log(`CMP => No IAB cookie present`); 46 | resolve(false); 47 | } 48 | }); 49 | }); 50 | } 51 | 52 | function readCookieSync(name = 'euconsent') { 53 | const value = '; ' + document.cookie; 54 | const parts = value.split('; ' + name + '='); 55 | if (parts.length === 2) { 56 | return parts.pop().split(';').shift(); 57 | } 58 | return undefined; 59 | } 60 | 61 | function writeCookie(value) { 62 | document.cookie = `${COOKIE_NAME}=${value}${COOKIE_DOMAIN};path=${PATH};max-age=${COOKIE_MAX_AGE}`; 63 | return Promise.resolve(true); 64 | } 65 | 66 | function writeCookieCustom(value) { 67 | if (value !== '[]') { 68 | document.cookie = `custom=${value}${COOKIE_DOMAIN};path=${PATH};max-age=${COOKIE_MAX_AGE}`; 69 | } 70 | } 71 | 72 | function readCookie(name = 'euconsent') { 73 | const value = '; ' + document.cookie; 74 | const parts = value.split('; ' + name + '='); 75 | if (parts.length === 2) { 76 | return Promise.resolve(parts.pop().split(';').shift()); 77 | } 78 | return Promise.resolve(); 79 | } 80 | 81 | function requestHttpCookies(cookieName, cookieValue) { 82 | console.log('[INFO] Requesting HTTP cookie from server.'); 83 | const url = __webpack_public_path__ + 'api/setCookie'; 84 | return new Promise((resolve, reject) => { 85 | const headers = new Headers(); 86 | headers.append('Content-Type', 'text/plain'); 87 | headers.append('Accept', 'application/json'); 88 | fetch(`${url}?n=${cookieName}&c=${cookieValue}`, { 89 | credentials: 'include', 90 | mode: 'cors', 91 | headers, 92 | }) 93 | .then((response) => { 94 | console.log(response); 95 | }) 96 | .catch((err) => { 97 | console.error(err); 98 | }); 99 | resolve(true); 100 | }); 101 | } 102 | 103 | export { 104 | readCookie, 105 | writeCookie, 106 | writeCookieCustom, 107 | readCookieSync, 108 | checkCookiesEnabled, 109 | checkCookiesEnabledPromise, 110 | checkIabCookie, 111 | requestHttpCookies, 112 | }; 113 | -------------------------------------------------------------------------------- /src/utils/iabListTransforms.js: -------------------------------------------------------------------------------- 1 | function fetchAllVendorsArray(vendorList) { 2 | return vendorList.vendors.map(item => item.id); 3 | } 4 | 5 | function fetchAllPurposeArray(vendorList) { 6 | return vendorList.purposes.map(item => item.id); 7 | } 8 | 9 | export { 10 | fetchAllVendorsArray, 11 | fetchAllPurposeArray, 12 | }; 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // WEBPACK PLUGINS 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin'); 6 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 7 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 8 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer'); 9 | 10 | const isProduction = process.env.NODE_ENV === 'production'; 11 | 12 | const config = { 13 | entry: { 14 | cmp: ['babel-polyfill', './src/main.js'], 15 | }, 16 | output: { 17 | path: path.resolve(__dirname, 'dist'), 18 | publicPath: process.env.ASSET_PATH, 19 | filename: '[name].bundle.js', 20 | chunkFilename: '[name].chunk.bundle.js', 21 | }, 22 | devtool: 'source-map', 23 | resolve: { 24 | alias: { 25 | '@': path.resolve('src'), 26 | }, 27 | }, 28 | module: { 29 | rules: [{ 30 | test: /\.vue$/, 31 | loader: 'vue-loader', 32 | }, 33 | { 34 | test: /\.js$/, 35 | exclude: /(node_modules|bower_components)/, 36 | use: { 37 | loader: 'babel-loader', 38 | }, 39 | }, 40 | { 41 | test: /\.css$/, 42 | use: [{ 43 | loader: 'style-loader', 44 | }, 45 | { 46 | loader: 'css-loader', 47 | }, 48 | ], 49 | }, 50 | { 51 | test: /\.scss$/, 52 | use: [ 53 | 'vue-style-loader', 54 | 'css-loader', 55 | { 56 | loader: 'sass-loader', 57 | }, 58 | ], 59 | }, 60 | { 61 | test: /\.(html)$/, 62 | use: { 63 | loader: 'html-loader', 64 | }, 65 | }, 66 | ], 67 | }, 68 | plugins: [ 69 | new VueLoaderPlugin(), 70 | new CleanWebpackPlugin('dist', {}), 71 | new HtmlWebpackPlugin({ 72 | template: './build/template.index.html', 73 | }), 74 | new HtmlWebpackExcludeAssetsPlugin(), 75 | new BundleAnalyzerPlugin.BundleAnalyzerPlugin({ 76 | analyzerMode: (isProduction) ? 'disabled' : 'server', 77 | }), 78 | ], 79 | }; 80 | 81 | module.exports = config; 82 | --------------------------------------------------------------------------------