├── .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 |
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 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------
/docs/images/control-flow.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
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 |
2 |