diff --git a/.babelrc b/.babelrc index 395e4c6b..82a743d7 100644 --- a/.babelrc +++ b/.babelrc @@ -7,6 +7,7 @@ "plugins": [ ["transform-decorators-legacy"], ["transform-react-jsx", { "pragma": "h" }], - ["transform-object-assign"] + ["transform-object-assign"], + ["transform-es2015-parameters"] ] } diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..ff866096 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/s1/loader.js diff --git a/.eslintrc b/.eslintrc index c7851c6e..bc7544f0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -24,7 +24,8 @@ } }, "globals": { - "sleep": 1 + "sleep": 1, + "define": 1 }, "rules": { "react/jsx-no-bind": [2, { "ignoreRefs": true }], diff --git a/.gitignore b/.gitignore index 5327fa86..60c4dd43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /node_modules /npm-debug.log /build +/dist .DS_Store /coverage /.idea diff --git a/.node_version b/.node_version new file mode 100644 index 00000000..dba04c1e --- /dev/null +++ b/.node_version @@ -0,0 +1 @@ +8.11.3 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..dc143956 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ + + +## [0.0.1](https://github.com/openmail/system1-cmp/compare/v0.0.0...v0.0.1) (2018-07-02) + +### Features + + - [x] Initial Release! + - [x] Versioned Deployment and updated README for installing CMP diff --git a/README.md b/README.md index ff6206ab..19cad583 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,244 @@ +# system1-cmp + +System1-CMP is a container around the [appnexus-cmp](https://github.com/appnexus/cmp) that provides additional features for loading the CMP and for providing a complete CMP solution. We would like to customize the CMP but still be able to upgrade the CMP SDK as the upstream [appnexus-cmp](https://github.com/appnexus/cmp) improves over time. + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + + - [Installation](#installation) + - [CMP Loader API](#cmp-loader-api) + - [init](#init) + - [init: config](#init-config) + - [init: callback](#init-callback) + - [Arguments](#arguments) + - [Possible Commands](#possible-commands) + - [Examples](#examples) + - [Events](#events) + - [Examples](#examples-1) + - [Build](#build) + - [Deploy](#deploy) + - [Upload](#upload) +- [AppNexus CMP](#appnexus-cmp) + - [Installation](#installation-1) + - [Build for Production](#build-for-production) + - [Documentation](#documentation) + - [Development](#development) + - [Testing](#testing) + + + +## Installation + +Check the version you want to use, new versions are opt-in only. + +``` + + + + + + +``` + +## CMP Loader API + +The CMP Loader exposes access to the underlying CMP SDK: [appnexus-cmp API](http://s.flocdn.com/cmp/docs/#/cmp-api) and externalizes configuration of the CMP SDK. + +### init + +You must call `init` explicitly to start the CMP. Otherwise, the CMP Loader will only queue commands and not initialize the CMP SDK. + + * `config` is REQUIRED + * `callback` is OPTIONAL + +``` +cmp('init', config, callback); +``` + +#### init: config + +`config` is a required argument of `init`. It allows us to configure/customize the CMP. + +Example Configuration: + +``` +const config = { + scriptSrc: '//s.flocdn.com/cmp/s1.cmp.js', + gdprApplies: true, + pubVendorListLocation: '//s.flocdn.com/cmp/pubvendors.json', // OPTIONAL, whitelists vendors + logging: false, + customPurposeListLocation: './purposes.json', + globalVendorListLocation: '//vendorlist.consensu.org/vendorlist.json', + globalConsentLocation: './portal.html', + storeConsentGlobally: false, + storePublisherData: false, + localization: {}, + forceLocale: null, + allowedVendorIds: null +} +cmp('init', config); +``` + +- `scriptSrc`: String: Required: location of CMP SDK. +- `gdprApplies`: Booelan: Enable / disable load of the CMP SDK +- `pubVendorListLocation`: OPTIONAL: location of pub vendor list +- `globalVendorListLocation`: OPTIONAL: global vendorList is managed by the IAB. + +#### init: callback + +Use the callback to determine if you should show the consent tool or not. + +- `errorMsg`: STRING // detail on the result CMP initializing +- `gdprApplies`: BOOLEAN // true if in EU, false if consent not required +- `hasConsented`: BOOLEAN // true if use has consented to all permissions +- `vendorConsents`: OBJECT +- `vendorList`: OBJECT + +Callback Example + +``` + + +cmp('init', { + gdprApplies: true, + scriptSrc: '//s.flocdn.com/cmp/s1.cmp.js' + }, (result) => { + + // Consent is required and there was an error + if (result.gdprApplies && result.errorMsg) { + cmp('showConsentTool'); + } + + // Consent is required and a user has not consented to all permissions + if (result.gdprApplies && !result.hasConsented) { + cmp('showConsentTool'); + } +}); +``` + +### Arguments + +``` +cmp(command, [parameter], [callback]) +``` + +- `command` (String): Name of the command to execute +- `[parameter]` (\*): Parameter to be passed to the command function +- `[callback]` (Function): Function to be executed with the result of the command + +### Possible Commands + +- `init` REQUIRED, can only be called once +- `addEventListener` +- `removeEventListener` +- `showConsentTool` + +- `getVendorConsents` +- `getPublisherConsents` +- `getConsentData` +- `getVendorList` + +### Examples +``` +cmp('getVendorConsents', null, (response) => console.log(response)); +cmp('getPublisherConsents', null, (response) => console.log(response)); +cmp('getConsentData', null, (response) => console.log(response)); +cmp('showConsentTool'); +``` + +## Events + +- `onConsentChanged` CUSTOM: triggers whenever consent has changed +- `isLoaded` +- `cmpReady` +- `onSubmit` + +### Examples + +``` +cmp('addEventListener', 'onConsentChanged', (event) => console.log(event)); +cmp('addEventListener', 'onSubmit', (event) => console.log(event)); +``` + +## Build + +Build the original project and the System1 project: +``` +yarn build # builds both projects +yarn build:s1 # builds just the System1 loader and "complete" CMP SDK. +yarn build:original # builds just the original CMP provided by appnexus +``` + +## Deploy + +Deploy will build and upload the project files to a System1 S3 bucket for public use + +``` +yarn deploy # builds and uploads both projects +yarn build:s1 # builds and uploads just the versioned S1 project +yarn build:original # builds and uploads just the original project +``` + +## Upload + +The system1-cmp project lives in `dist/{version}` and is immutable, you can't upload it more than once. +This *should* be a safe operation to make as you'll just see an error in the terminal telling you the files already exist. +You will need to bump the `package.json` version in order to publish any changes to S3. + +``` +yarn upload:s1 +``` + +The original project gets deployed to S3, but it's for reference only, it will invalidate automatically, and no one should have a production dependency on it. +This should be a safe operation as it does not affect production files. + +``` +yarn upload:original +``` + [![Build Status](https://travis-ci.org/appnexus/cmp.svg?branch=master)](https://travis-ci.org/appnexus/cmp) # AppNexus CMP diff --git a/config/common.webpack.config.babel.js b/config/common.webpack.config.babel.js new file mode 100644 index 00000000..b3b23e9d --- /dev/null +++ b/config/common.webpack.config.babel.js @@ -0,0 +1,163 @@ +/* @noflow */ +/* eslint-disable import/no-extraneous-dependencies */ + +import webpack from 'webpack'; +import autoprefixer from 'autoprefixer'; +import path from 'path'; +import packageJson from '../package.json'; + +const ENV = process.env.NODE_ENV || 'development'; +const CSS_MAPS = ENV !== 'production'; +const { version } = packageJson; + +const uglifyPlugin = new webpack.optimize.UglifyJsPlugin({ + output: { + comments: false + }, + compress: { + unsafe_comps: true, + properties: true, + keep_fargs: false, + pure_getters: true, + collapse_vars: true, + unsafe: true, + warnings: false, + screw_ie8: true, + sequences: true, + dead_code: true, + drop_debugger: true, + comparisons: true, + conditionals: true, + evaluate: true, + booleans: true, + loops: true, + unused: true, + hoist_funs: true, + if_return: true, + join_vars: true, + cascade: true, + drop_console: false + } +}); + +const commonConfig = { + context: path.resolve(__dirname, '../', 'src'), + resolve: { + extensions: ['.jsx', '.js', '.json', '.less'], + modules: [ + path.resolve(__dirname, '../', 'src/lib'), + path.resolve(__dirname, '../node_modules'), + 'node_modules' + ], + alias: { + components: path.resolve(__dirname, '../', 'src/components'), // used for tests + style: path.resolve(__dirname, '../', 'src/style'), + react: 'preact-compat', + 'react-dom': 'preact-compat' + } + }, + + module: { + rules: [ + { + test: /\.hbs/, + exclude: /node_modules/, + use: 'handlebars-loader' + }, + { + test: /\.jsx?$/, + exclude: path.resolve(__dirname, '../', 'src'), + enforce: 'pre', + use: 'source-map-loader' + }, + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: 'babel-loader' + }, + { + // Transform our own .(less|css) files with PostCSS and CSS-modules + test: /\.(less|css)$/, + include: [ + path.resolve(__dirname, '../', 'src/components'), + path.resolve(__dirname, '../', 'src/docs/components') + ], + use: [ + { + loader: 'style-loader' + }, + { + loader: 'css-loader', + options: { + modules: true, + sourceMap: CSS_MAPS, + importLoaders: 1, + minimize: true, + localIdentName: '[name]_[local]--[hash:base64:5]' + } + }, + { + loader: 'postcss-loader', + options: { + sourceMap: CSS_MAPS, + plugins: () => { + autoprefixer({ browsers: ['last 2 versions'] }); + } + } + }, + { + loader: 'less-loader', + options: { sourceMap: CSS_MAPS } + } + ] + }, + { + test: /\.(less|css)$/, + include: [ + path.resolve(__dirname, '../', 'src/docs/style'), + path.resolve( + __dirname, + '../node_modules/codemirror/lib/codemirror.css' + ) + ], + use: [ + { + loader: 'style-loader' + }, + { + loader: 'css-loader' + }, + { + loader: 'less-loader', + options: { sourceMap: CSS_MAPS } + } + ] + }, + { + test: /\.json$/, + use: 'json-loader' + }, + { + test: /\.(xml|html|txt|md)$/, + use: 'raw-loader' + }, + { + test: /\.(svg|woff2?|ttf|eot|jpe?g|png|gif)(\?.*)?$/i, + use: ENV === 'production' ? 'file-loader' : 'url-loader' + } + ] + }, + + stats: { colors: true }, + + node: { + global: true, + process: false, + Buffer: false, + __filename: false, + __dirname: false, + setImmediate: false + } +}; + +export { commonConfig, ENV, CSS_MAPS, version, uglifyPlugin }; diff --git a/config/s1.webpack.config.babel.js b/config/s1.webpack.config.babel.js new file mode 100644 index 00000000..e81150e9 --- /dev/null +++ b/config/s1.webpack.config.babel.js @@ -0,0 +1,55 @@ +/* @noflow */ +/* eslint-disable import/no-extraneous-dependencies */ +import webpack from 'webpack'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import path from 'path'; +import fs from 'fs'; +import UglifyJS from 'uglify-es'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; + +import { + commonConfig, + uglifyPlugin, + version +} from './common.webpack.config.babel'; + +const ENV = process.env.NODE_ENV || 'development'; + +module.exports = [ + // S1 CMP + { + entry: { + cmp: './s1/cmp.js' + }, + ...commonConfig, + output: { + path: path.resolve(__dirname, '../', `dist/${version}`), + publicPath: './', + filename: 'cmp.js' + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new HtmlWebpackPlugin({ + filename: '../reference.html', + template: 's1/reference.hbs', + inject: false, + inline: UglifyJS.minify(fs.readFileSync('./src/s1/loader.js', 'utf8')).code, + version + }), + new CopyWebpackPlugin([ + { + from: 's1/loader.js', + to: './loader.js', + transform(content) { + // Just want to uglify and copy this file over + return Promise.resolve( + Buffer.from(UglifyJS.minify(content.toString()).code, 'utf8') + ); + } + } + ]) + ].concat(ENV === 'production' ? uglifyPlugin : []) + } +]; diff --git a/config/webpack.config.babel.js b/config/webpack.config.babel.js new file mode 100644 index 00000000..9fe1e4b1 --- /dev/null +++ b/config/webpack.config.babel.js @@ -0,0 +1,85 @@ +/* @noflow */ +/* eslint-disable import/no-extraneous-dependencies */ + +import webpack from 'webpack'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import path from 'path'; +import { + commonConfig, + uglifyPlugin +} from './common.webpack.config.babel'; + +const ENV = process.env.NODE_ENV || 'development'; + + +module.exports = [ + // CMP + { + entry: { + cmp: './index.js', + 'cmp.complete': './complete.js' + }, + + output: { + path: path.resolve(__dirname, '../', 'dist'), + publicPath: './', + filename: '[name].bundle.js' + }, + ...commonConfig, + plugins: [ + new webpack.NoEmitOnErrorsPlugin(), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new webpack.ProvidePlugin({ + Promise: 'promise-polyfill' + }), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + chunks: ['cmp'] + }) + ].concat(ENV === 'production' ? uglifyPlugin : []) + }, + // Docs config + { + entry: { + docs: './docs/index.jsx', + iframeExample: './docs/iframe/iframeExample.jsx', + portal: './docs/assets/portal.js' + }, + + output: { + path: path.resolve(__dirname, '../', 'dist/docs'), + publicPath: './', + filename: '[name].bundle.js' + }, + ...commonConfig, + plugins: [ + new webpack.NoEmitOnErrorsPlugin(), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new webpack.ProvidePlugin({ + Promise: 'promise-polyfill' + }), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'docs/index.html', + chunks: ['docs'] + }), + new HtmlWebpackPlugin({ + filename: 'iframeExample.html', + template: './docs/iframe/iframeExample.html', + chunks: ['iframeExample'] + }), + new HtmlWebpackPlugin({ + filename: 'portal.html', + template: './docs/assets/portal.html', + chunks: ['portal'] + }), + new CopyWebpackPlugin([{ from: 'docs/assets', to: '.' }]) + ].concat(ENV === 'production' ? uglifyPlugin : []) + } +]; diff --git a/package.json b/package.json index ef6429e1..34bcb918 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,21 @@ { "name": "appnexus-cmp", - "version": "0.0.0", + "version": "0.0.1", "scripts": { - "clean": "rm -rf build", - "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress", - "start": "serve build -s -c 1", + "clean": "rimraf ./dist", + "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress --config config/webpack.config.babel.js", + "dev:s1": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress --config config/s1.webpack.config.babel.js", + "start": "serve dist -s -c 1", "prestart": "npm run build", - "build": "cross-env NODE_ENV=production webpack --progress", - "prebuild": "npm run clean && mkdirp build", + "build": "rimraf ./dist && yarn build:original && yarn build:s1", + "deploy": "rimraf ./dist && yarn deploy:original && yarn deploy:s1", + "build:original": "cross-env NODE_ENV=production webpack --progress --config config/webpack.config.babel.js", + "deploy:original": "yarn build:original && yarn upload:original", + "upload:original": "cross-var s3-deploy './dist/{*.?(js|html),docs/**}' --cwd './dist' --region us-west-2 --bucket s1-layout-cdn/cmp --gzip --cache 1440 --invalidate '/cmp/*.js /cmp/* /cmp/docs/*'", + "build:s1": "cross-env NODE_ENV=production webpack --progress --config config/s1.webpack.config.babel.js", + "deploy:s1": "yarn build:s1 && yarn upload:s1", + "upload:s1": "cross-var s3-deploy './dist/$npm_package_version/**' --cwd './dist/$npm_package_version' --region us-west-2 --bucket s1-layout-cdn/cmp/$npm_package_version --gzip --preventUpdates --immutable --ext js", + "prebuild": "npm run clean && mkdirp dist", "test": "npm run -s lint && jest --coverage", "test:watch": "npm run -s test -- --watch", "lint": "eslint src test", @@ -60,13 +68,17 @@ "chai": "^4.0.2", "copy-webpack-plugin": "^4.3.1", "cross-env": "^5.0.1", + "cross-var": "^1.1.0", "css-loader": "^0.28.0", "eslint": "^4.1.0", "eslint-plugin-jest": "^21.7.0", "eslint-plugin-react": "^7.0.0", "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^1.1.6", - "html-webpack-plugin": "^2.28.0", + "fs": "^0.0.1-security", + "handlebars-loader": "^1.7.0", + "html-webpack-inline-source-plugin": "^0.0.10", + "html-webpack-plugin": "^3.0.6", "identity-obj-proxy": "^3.0.0", "jest": "^22.2.1", "json-loader": "^0.5.4", @@ -79,11 +91,14 @@ "raw-loader": "^1.0.0-beta.0", "regenerator-runtime": "^0.11.1", "replace-bundle-webpack-plugin": "^1.0.0", - "script-ext-html-webpack-plugin": "^1.3.4", + "rimraf": "2.6.2", + "s3-deploy": "1.0.1", + "script-ext-html-webpack-plugin": "^2.0.1", "sinon": "^4.2.2", "sinon-chai": "^2.9.0", "source-map-loader": "^0.2.1", "style-loader": "^0.20.1", + "uglify-es": "^3.3.9", "url-loader": "^0.6.1", "webpack": "^3.0.0", "webpack-dev-server": "^2.4.4" diff --git a/src/assets/pubvendors.json b/src/assets/pubvendors.json new file mode 100644 index 00000000..2397b917 --- /dev/null +++ b/src/assets/pubvendors.json @@ -0,0 +1,43 @@ +{ + "publisherVendorsVersion": 1, + "globalVendorListVersion": 15, + "lastUpdated": "2018-05-07T15:59:08Z", + "vendors": [ + { + "id": 6 + }, + { + "id": 30 + }, + { + "id": 24 + }, + { + "id": 29 + }, + { + "id": 39 + }, + { + "id": 11 + }, + { + "id": 15 + }, + { + "id": 13 + }, + { + "id": 34 + }, + { + "id": 32 + }, + { + "id": 10 + }, + { + "id": 57 + } + ] +} diff --git a/src/assets/purposes.json b/src/assets/purposes.json new file mode 100644 index 00000000..1f3ca708 --- /dev/null +++ b/src/assets/purposes.json @@ -0,0 +1,1620 @@ +{ + "vendorListVersion": 26, + "lastUpdated": "2018-05-22T16:00:12Z", + "purposes": [ + { + "id": 1, + "name": "Information storage and access", + "description": + "The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies." + }, + { + "id": 2, + "name": "Personalisation", + "description": + "The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content." + }, + { + "id": 3, + "name": "Ad selection, delivery, reporting", + "description": + "The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time." + }, + { + "id": 4, + "name": "Content selection, delivery, reporting", + "description": + "The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time." + } + ], + "features": [ + { + "id": 1, + "name": "Matching Data to Offline Sources", + "description": + "Combining data from offline sources that were initially collected in other contexts." + }, + { + "id": 2, + "name": "Linking Devices", + "description": + "Allow processing of a user's data to connect such user across multiple devices." + }, + { + "id": 3, + "name": "Precise Geographic Location Data", + "description": + "Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent." + } + ], + "vendors": [ + { + "id": 8, + "name": "Emerse Sverige AB", + "policyUrl": "https://www.emerse.com/privacy-policy/", + "purposeIds": [1, 2, 4], + "legIntPurposeIds": [3, 5], + "featureIds": [1, 2] + }, + { + "id": 12, + "name": "BeeswaxIO Corporation", + "policyUrl": "https://www.beeswax.com/privacy.html", + "purposeIds": [1, 3, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 28, + "name": "TripleLift, Inc.", + "policyUrl": "https://triplelift.com/privacy/", + "purposeIds": [1, 3], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 9, + "name": "AdMaxim Inc.", + "policyUrl": "http://www.admaxim.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 27, + "name": "ADventori SAS", + "policyUrl": "https://www.adventori.com/with-us/legal-notice/", + "purposeIds": [2], + "legIntPurposeIds": [1, 3, 4, 5], + "featureIds": [] + }, + { + "id": 25, + "name": "Oath (EMEA) Limited", + "policyUrl": "https://policies.oath.com/ie/en/oath/privacy/index.html", + "purposeIds": [1, 2], + "legIntPurposeIds": [3, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 26, + "name": "Venatus Media Limited", + "policyUrl": "https://www.venatusmedia.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 1, + "name": "Exponential Interactive, Inc", + "policyUrl": "http://exponential.com/privacy", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 6, + "name": "AdSpirit GmbH", + "policyUrl": "http://www.adspirit.de/privacy", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 30, + "name": "BidTheatre AB", + "policyUrl": "https://www.bidtheatre.com/privacy-policy", + "purposeIds": [2], + "legIntPurposeIds": [1, 3], + "featureIds": [2, 3] + }, + { + "id": 24, + "name": "Conversant Europe Ltd.", + "policyUrl": "https://www.conversantmedia.eu/legal/privacy-policy", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 29, + "name": "Etarget SE", + "policyUrl": "https://www.etarget.sk/privacy.php", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1] + }, + { + "id": 39, + "name": "ADITION technologies AG", + "policyUrl": "adition.com/datenschutz", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 11, + "name": "Quantcast International Limited", + "policyUrl": "https://www.quantcast.com/privacy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1, 2] + }, + { + "id": 15, + "name": "Adikteev", + "policyUrl": "https://www.adikteev.com/eu/privacy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 4, + "name": "Roq.ad GmbH", + "policyUrl": "https://www.roq.ad/privacy-policy", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [2, 3] + }, + { + "id": 7, + "name": "Vibrant Media Limited", + "policyUrl": "https://www.vibrantmedia.com/en/privacy-policy/", + "purposeIds": [2, 3, 4, 5], + "legIntPurposeIds": [1], + "featureIds": [] + }, + { + "id": 2, + "name": "Captify Technologies Limited", + "policyUrl": "http://www.captify.co.uk/privacy-policy/", + "purposeIds": [2, 3, 5], + "legIntPurposeIds": [1], + "featureIds": [2] + }, + { + "id": 37, + "name": "NEURAL.ONE", + "policyUrl": "https://web.neural.one/privacy-policy/", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2] + }, + { + "id": 13, + "name": "Sovrn Holdings Inc", + "policyUrl": "https://www.sovrn.com/sovrn-privacy/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [2, 3] + }, + { + "id": 34, + "name": "NEORY GmbH", + "policyUrl": "https://www.neory.com/privacy.html", + "purposeIds": [1, 2, 4, 5], + "legIntPurposeIds": [3], + "featureIds": [] + }, + { + "id": 32, + "name": "AppNexus Inc.", + "policyUrl": + "https://www.appnexus.com/en/company/platform-privacy-policy", + "purposeIds": [1], + "legIntPurposeIds": [3], + "featureIds": [2, 3] + }, + { + "id": 10, + "name": "Index Exchange, Inc. ", + "policyUrl": "www.indexexchange.com/privacy", + "purposeIds": [1], + "legIntPurposeIds": [], + "featureIds": [2, 3] + }, + { + "id": 57, + "name": "ADARA MEDIA UNLIMITED", + "policyUrl": "https://adara.com/2018/04/10/adara-gdpr-faq/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2] + }, + { + "id": 63, + "name": "Avocet Systems Limited", + "policyUrl": "http://www.avocet.io/privacy-policy", + "purposeIds": [], + "legIntPurposeIds": [1, 3], + "featureIds": [] + }, + { + "id": 51, + "name": "xAd, Inc. dba GroundTruth", + "policyUrl": "https://www.groundtruth.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 49, + "name": "Tradelab, SAS", + "policyUrl": "http://tradelab.com/en/privacy/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [5], + "featureIds": [1, 2, 3] + }, + { + "id": 45, + "name": "Smart Adserver", + "policyUrl": "http://smartadserver.com/company/privacy-policy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3, 5], + "featureIds": [3] + }, + { + "id": 52, + "name": "The Rubicon Project, Limited", + "policyUrl": + "http://rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [3] + }, + { + "id": 35, + "name": "Purch Group, Inc.", + "policyUrl": "http://www.purch.com/privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [3, 5], + "featureIds": [] + }, + { + "id": 71, + "name": "Dataxu, Inc. ", + "policyUrl": + "https://www.dataxu.com/about-us/privacy/data-collection-platform/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 79, + "name": "MediaMath, Inc.", + "policyUrl": "http://www.mediamath.com/privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 91, + "name": "Criteo SA", + "policyUrl": "https://www.criteo.com/privacy/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [1, 2] + }, + { + "id": 85, + "name": "Crimtan Holdings Limited", + "policyUrl": "https://crimtan.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 16, + "name": "RTB House S.A.", + "policyUrl": "https://www.rtbhouse.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 86, + "name": "Scene Stealer Limited", + "policyUrl": "http://scenestealer.tv/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 94, + "name": "Blis Media Limited", + "policyUrl": "http://www.blis.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 73, + "name": "Simplifi Holdings Inc.", + "policyUrl": "https://www.simpli.fi/site-privacy-policy2/", + "purposeIds": [2, 3, 4, 5], + "legIntPurposeIds": [1], + "featureIds": [2, 3] + }, + { + "id": 67, + "name": "LifeStreet Corporation", + "policyUrl": "http://www.lifestreet.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 33, + "name": "ShareThis, Inc.", + "policyUrl": "http://www.sharethis.com/privacy/", + "purposeIds": [2, 3, 4], + "legIntPurposeIds": [1, 5], + "featureIds": [] + }, + { + "id": 20, + "name": "N Technologies Inc.", + "policyUrl": "https://n.rich/privacy-notice", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [2] + }, + { + "id": 55, + "name": "Madison Logic, Inc.", + "policyUrl": "https://www.madisonlogic.com/privacy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 53, + "name": "Sirdata", + "policyUrl": "https://www.sirdata.com/privacy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2] + }, + { + "id": 69, + "name": "OpenX Software Ltd. and its affiliates", + "policyUrl": "https://www.openx.com/legal/privacy-policy/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 98, + "name": "mPlatform", + "policyUrl": "https://www.groupm.com/mplatform-privacy-policy", + "purposeIds": [1, 2, 3, 4], + "legIntPurposeIds": [5], + "featureIds": [1, 2, 3] + }, + { + "id": 62, + "name": "Justpremium BV", + "policyUrl": "http://justpremium.com/privacy-policy/", + "purposeIds": [1, 3], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 19, + "name": "Intent Media, Inc.", + "policyUrl": "https://intentmedia.com/privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [2] + }, + { + "id": 43, + "name": "Vdopia DBA Chocolate Platform", + "policyUrl": "https://chocolateplatform.com/privacy-policy/", + "purposeIds": [1, 3], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 36, + "name": "RhythmOne, LLC", + "policyUrl": "https://www.rhythmone.com/privacy-policy", + "purposeIds": [5], + "legIntPurposeIds": [1, 2, 3, 4], + "featureIds": [1, 2, 3] + }, + { + "id": 80, + "name": "Sharethrough, Inc", + "policyUrl": "https://platform-cdn.sharethrough.com/privacy-policy", + "purposeIds": [3, 5], + "legIntPurposeIds": [1], + "featureIds": [] + }, + { + "id": 81, + "name": "PulsePoint, Inc.", + "policyUrl": "https://www.pulsepoint.com/privacy-policy", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 23, + "name": "Amobee, Inc. ", + "policyUrl": "https://www.amobee.com/trust/privacy-guidelines", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 75, + "name": "M32 Media Inc", + "policyUrl": "https://m32.media/privacy-cookie-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 17, + "name": "Greenhouse Group BV (with its trademark LemonPI)", + "policyUrl": "https://www.lemonpi.io/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [2] + }, + { + "id": 61, + "name": "GumGum, Inc.", + "policyUrl": "https://gumgum.com/privacy-policy", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 40, + "name": "Active Agent AG", + "policyUrl": + "http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 76, + "name": "PubMatic, Inc.", + "policyUrl": "https://pubmatic.com/privacy-policy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3, 4, 5], + "featureIds": [] + }, + { + "id": 89, + "name": "Tapad, Inc. ", + "policyUrl": "https://www.tapad.com/privacy", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 5], + "featureIds": [2] + }, + { + "id": 46, + "name": "Skimbit Ltd", + "policyUrl": "https://skimlinks.com/pages/privacy-policy", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [5], + "featureIds": [2] + }, + { + "id": 66, + "name": "adsquare GmbH", + "policyUrl": "www.adsquare.com/privacy", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 105, + "name": "Impression Desk Technologies Limited", + "policyUrl": "impressiondesk.com", + "purposeIds": [1, 3, 5], + "legIntPurposeIds": [], + "featureIds": [2, 3] + }, + { + "id": 41, + "name": "Adverline", + "policyUrl": "https://www.adverline.com/privacy/", + "purposeIds": [2], + "legIntPurposeIds": [1, 3], + "featureIds": [] + }, + { + "id": 3, + "name": "affilinet", + "policyUrl": "https://www.affili.net/de/footeritem/datenschutz", + "purposeIds": [2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 82, + "name": "Smaato, Inc.", + "policyUrl": "https://www.smaato.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 60, + "name": "Rakuten Marketing LLC", + "policyUrl": + "https://rakutenmarketing.com/legal-notices/services-privacy-policy", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 70, + "name": "Yieldlab AG", + "policyUrl": "http://www.yieldlab.de/meta-navigation/datenschutz/", + "purposeIds": [], + "legIntPurposeIds": [1, 3], + "featureIds": [3] + }, + { + "id": 50, + "name": "Adform A/S", + "policyUrl": "https://site.adform.com/privacy-policy-opt-out/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1, 2] + }, + { + "id": 48, + "name": "NetSuccess, s.r.o.", + "policyUrl": "https://www.inres.sk/pp/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 100, + "name": "Fifty Technology Limited", + "policyUrl": "https://fiftymedia.com/privacy-policy/", + "purposeIds": [2, 3, 5], + "legIntPurposeIds": [1], + "featureIds": [] + }, + { + "id": 21, + "name": "The Trade Desk, Inc and affiliated companies", + "policyUrl": "https://www.thetradedesk.com/general/privacy-policy", + "purposeIds": [1, 2], + "legIntPurposeIds": [3], + "featureIds": [1, 2, 3] + }, + { + "id": 110, + "name": "Hottraffic BV (DMA Institute)", + "policyUrl": + "https://www.dma-institute.com/additional-information-for-data-subjects/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [] + }, + { + "id": 42, + "name": "Taboola Europe Limited", + "policyUrl": "https://www.taboola.com/privacy-policy", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1, 2] + }, + { + "id": 112, + "name": "Maytrics GmbH", + "policyUrl": "https://maytrics.com/node/2", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 77, + "name": "comScore, Inc.", + "policyUrl": "https://www.comscore.com/About-comScore/Privacy-Policy", + "purposeIds": [1, 5], + "legIntPurposeIds": [], + "featureIds": [2] + }, + { + "id": 109, + "name": "LoopMe Ltd", + "policyUrl": "https://loopme.com/privacy/", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 120, + "name": "Eyeota Ptd Ltd", + "policyUrl": "https://www.eyeota.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2], + "featureIds": [1] + }, + { + "id": 93, + "name": "Adloox SA", + "policyUrl": "http://adloox.com/disclaimer", + "purposeIds": [], + "legIntPurposeIds": [1, 5], + "featureIds": [] + }, + { + "id": 132, + "name": "Teads ", + "policyUrl": "https://teads.tv/privacy-policy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3], + "featureIds": [1, 2] + }, + { + "id": 22, + "name": "admetrics GmbH", + "policyUrl": "https://admetrics.io/en/privacy_policy/", + "purposeIds": [1], + "legIntPurposeIds": [3, 4, 5], + "featureIds": [] + }, + { + "id": 102, + "name": "SlimCut Media SAS", + "policyUrl": "http://www.slimcutmedia.com/privacy-policy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3], + "featureIds": [] + }, + { + "id": 108, + "name": "Rich Audience", + "policyUrl": "https://richaudience.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 18, + "name": "Widespace AB", + "policyUrl": "https://www.widespace.com/legal/privacy-policy-notice/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 68, + "name": "Sizmek Technologies, Inc. ", + "policyUrl": "https://www.sizmek.com/privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [2] + }, + { + "id": 122, + "name": "Avid Media Ltd", + "policyUrl": "http://www.avidglobalmedia.eu/privacy-policy.html", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 118, + "name": "Drawbridge, Inc.", + "policyUrl": "http://www.drawbridge.com/privacy/", + "purposeIds": [1], + "legIntPurposeIds": [], + "featureIds": [2] + }, + { + "id": 97, + "name": "LiveRamp, Inc.", + "policyUrl": "www.liveramp.com/service-privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2] + }, + { + "id": 74, + "name": "Admotion SRL", + "policyUrl": "http://www.admotion.com/policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 3], + "featureIds": [] + }, + { + "id": 138, + "name": "ConnectAd Realtime GmbH", + "policyUrl": "http://connectadrealtime.com/privacy/", + "purposeIds": [1], + "legIntPurposeIds": [3], + "featureIds": [] + }, + { + "id": 95, + "name": "Lotame Solutions, Inc.", + "policyUrl": "https://www.lotame.com/about-lotame/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 72, + "name": "Nano Interactive GmbH", + "policyUrl": "http://www.nanointeractive.com/privacy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [] + }, + { + "id": 127, + "name": "PIXIMEDIA SAS", + "policyUrl": "https://piximedia.com/privacy/", + "purposeIds": [1, 2, 4], + "legIntPurposeIds": [3, 5], + "featureIds": [3] + }, + { + "id": 136, + "name": "Str\u00f6er SSP GmbH", + "policyUrl": + "https://www.stroeer.de/fileadmin/user_upload/Datenschutz.pdf", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [2, 3] + }, + { + "id": 111, + "name": "ShowHeroes GmbH", + "policyUrl": "http://showheroes.com/privacy", + "purposeIds": [], + "legIntPurposeIds": [5], + "featureIds": [3] + }, + { + "id": 56, + "name": "Confiant Inc.", + "policyUrl": "https://www.confiant.com/privacy", + "purposeIds": [1], + "legIntPurposeIds": [], + "featureIds": [1] + }, + { + "id": 124, + "name": "Teemo SA", + "policyUrl": "https://teemo.co/fr/confidentialite/", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 154, + "name": "YOC AG", + "policyUrl": "https://yoc.com/privacy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3, 5], + "featureIds": [3] + }, + { + "id": 38, + "name": "Beemray Oy", + "policyUrl": "https://www.beemray.com/privacy-policy/", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 101, + "name": "MiQ", + "policyUrl": "http://wearemiq.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 149, + "name": "ADman Interactive SL", + "policyUrl": "http://admanmedia.com/politica", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [5], + "featureIds": [] + }, + { + "id": 151, + "name": "Admedo Ltd", + "policyUrl": "https://www.admedo.com/privacy-policy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3], + "featureIds": [3] + }, + { + "id": 153, + "name": "MADVERTISE MEDIA", + "policyUrl": "http://madvertise.com/en/gdpr/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 159, + "name": "Underdog Media LLC ", + "policyUrl": "https://underdogmedia.com/privacy-policy/", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 157, + "name": "Seedtag Advertising S.L", + "policyUrl": "https://www.seedtag.com/en/privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 145, + "name": "Snapsort Inc., operating as Sortable", + "policyUrl": "https://sortable.com/privacy", + "purposeIds": [1, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 131, + "name": "ID5 Technology SAS", + "policyUrl": "https://www.id5.io/privacy", + "purposeIds": [1], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 158, + "name": "Reveal Mobile, Inc", + "policyUrl": "revealmobile.com/privacy", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 147, + "name": "One Person Health, Inc. (DBA Adacado)", + "policyUrl": "https://www.adacado.com/privacy-policy-april-25-2018/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 130, + "name": "AdRoll Inc", + "policyUrl": "adrollgroup.com/privacy", + "purposeIds": [1], + "legIntPurposeIds": [2, 3], + "featureIds": [1, 2] + }, + { + "id": 129, + "name": "IPONWEB GmbH", + "policyUrl": "https://www.iponweb.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 128, + "name": "BIDSWITCH GmbH", + "policyUrl": "http://www.bidswitch.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 168, + "name": "EASYmedia GmbH", + "policyUrl": "https://login.rtbmarket.com/gdpr", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 164, + "name": "Outbrain UK Ltd", + "policyUrl": "https://www.outbrain.com/legal/", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [4], + "featureIds": [1] + }, + { + "id": 144, + "name": "district m inc.", + "policyUrl": "https://districtm.net/en/page/data-and-privacy-policy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3, 5], + "featureIds": [3] + }, + { + "id": 163, + "name": "Bombora Inc.", + "policyUrl": "https://bombora.com/privacy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2] + }, + { + "id": 173, + "name": "Yieldmo, Inc.", + "policyUrl": "https://www.yieldmo.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 88, + "name": "TreSensa, Inc.", + "policyUrl": "www.tresensa.com/eu-privacy", + "purposeIds": [1], + "legIntPurposeIds": [2, 3], + "featureIds": [] + }, + { + "id": 78, + "name": "Flashtalking, Inc.", + "policyUrl": "http://www.flashtalking.com/privacypolicy/", + "purposeIds": [1], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 59, + "name": "Sift Media, Inc", + "policyUrl": "https://www.sift.co/privacy", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 114, + "name": "Sublime Skinz", + "policyUrl": "http://ayads.co/privacy.php", + "purposeIds": [2, 3], + "legIntPurposeIds": [1, 5], + "featureIds": [] + }, + { + "id": 175, + "name": "FORTVISION", + "policyUrl": "http://fortvision.com/POC/index.html", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 133, + "name": "digitalAudience", + "policyUrl": "http://digitalaudience.io/legal/privacy-cookies/", + "purposeIds": [1, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 14, + "name": "Adkernel LLC", + "policyUrl": "http://adkernel.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [2, 3] + }, + { + "id": 180, + "name": "Thirdpresence Oy", + "policyUrl": "http://www.thirdpresence.com/privacy", + "purposeIds": [1], + "legIntPurposeIds": [3], + "featureIds": [3] + }, + { + "id": 183, + "name": "EMX Digital LLC", + "policyUrl": "https://emxdigital.com/privacy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3, 4, 5], + "featureIds": [2, 3] + }, + { + "id": 58, + "name": "33Across", + "policyUrl": "http://www.33across.com/privacy-policy", + "purposeIds": [1, 2, 3, 5], + "legIntPurposeIds": [], + "featureIds": [2] + }, + { + "id": 140, + "name": "Platform161", + "policyUrl": "https://platform161.com/cookie-and-privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 90, + "name": "Teroa S.A.", + "policyUrl": "https://www.e-planning.net/en/privacy.html", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [] + }, + { + "id": 141, + "name": "1020, Inc. dba Placecast and Ericsson Emodo", + "policyUrl": "https://www.ericsson-emodo.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4], + "legIntPurposeIds": [5], + "featureIds": [3] + }, + { + "id": 142, + "name": "Media.net Advertising FZ-LLC", + "policyUrl": "https://www.media.net/en/privacy-policy", + "purposeIds": [1, 3, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 209, + "name": "Delta Projects AB", + "policyUrl": "http://www.deltaprojects.com/data-collection-policy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 5], + "featureIds": [3] + }, + { + "id": 195, + "name": "advanced store GmbH", + "policyUrl": "http://www.advanced-store.com/de/datenschutz/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3], + "featureIds": [3] + }, + { + "id": 197, + "name": "Switch Concepts Limited", + "policyUrl": "https://www.switchconcepts.com/privacy-policy", + "purposeIds": [1, 3, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 190, + "name": "video intelligence AG", + "policyUrl": "https://www.vi.ai/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 84, + "name": "Semasio GmbH", + "policyUrl": "http://www.semasio.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 5], + "featureIds": [] + }, + { + "id": 65, + "name": "Location Sciences AI Ltd", + "policyUrl": "https://www.locationsciences.ai/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [3, 5], + "featureIds": [3] + }, + { + "id": 210, + "name": "Zemanta, Inc.", + "policyUrl": "http://www.zemanta.com/legal/privacy", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1] + }, + { + "id": 200, + "name": "Tapjoy, Inc.", + "policyUrl": "https://www.tapjoy.com/legal/#privacy-policy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [] + }, + { + "id": 188, + "name": "Sellpoints Inc.", + "policyUrl": "https://retargeter.com/service-privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 217, + "name": "2KDirect, Inc. (dba iPromote)", + "policyUrl": "https://www.ipromote.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 3], + "featureIds": [] + }, + { + "id": 156, + "name": "Centro, Inc.", + "policyUrl": "https://www.centro.net/privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [1] + }, + { + "id": 194, + "name": "Rezonence Limited", + "policyUrl": "https://rezonence.com/privacy-policy/", + "purposeIds": [3, 5], + "legIntPurposeIds": [1], + "featureIds": [] + }, + { + "id": 226, + "name": "Publicis Media GmbH", + "policyUrl": "https://www.publicismedia.de/datenschutz/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1] + }, + { + "id": 198, + "name": "SYNC", + "policyUrl": "https://redirect.sync.tv/privacy/", + "purposeIds": [1, 3], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 227, + "name": "ORTEC B.V.", + "policyUrl": "https://www.ortecadscience.com/privacy-policy/", + "purposeIds": [2], + "legIntPurposeIds": [1, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 225, + "name": "Ligatus GmbH", + "policyUrl": "https://www.ligatus.com/en/privacy-policy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [3] + }, + { + "id": 205, + "name": "Adssets AB", + "policyUrl": "http://adssets.com/policy/", + "purposeIds": [], + "legIntPurposeIds": [3, 5], + "featureIds": [] + }, + { + "id": 179, + "name": "Collective Europe Ltd.", + "policyUrl": "https://www.timeincuk.com/privacy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 31, + "name": "Ogury Ltd.", + "policyUrl": "https://www.ogury.com/privacy/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [5], + "featureIds": [] + }, + { + "id": 92, + "name": "1plusX AG", + "policyUrl": "https://www.1plusx.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 155, + "name": "AntVoice", + "policyUrl": "https://www.antvoice.com/en/privacypolicy/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [], + "featureIds": [2] + }, + { + "id": 115, + "name": "smartclip Holding AG", + "policyUrl": "http://privacy-portal.smartclip.net/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 126, + "name": "DoubleVerify Inc.\u200b", + "policyUrl": "https://www.doubleverify.com/privacy/", + "purposeIds": [1, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 193, + "name": "Mediasmart Mobile S.L.", + "policyUrl": "http://mediasmart.io/privacy/", + "purposeIds": [1, 3], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 245, + "name": "IgnitionOne", + "policyUrl": "https://www.ignitionone.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2] + }, + { + "id": 213, + "name": "emetriq GmbH", + "policyUrl": "https://www.emetriq.com/datenschutz/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [1, 2] + }, + { + "id": 244, + "name": "Leadplace - Temelio", + "policyUrl": "https://temelio.com/vie-privee", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2] + }, + { + "id": 224, + "name": "adrule GmbH", + "policyUrl": "https://www.adrule.net/de/datenschutz/", + "purposeIds": [2, 4], + "legIntPurposeIds": [1, 3, 5], + "featureIds": [3] + }, + { + "id": 174, + "name": "A Million Ads Limited", + "policyUrl": "https://www.amillionads.com/privacy-policy", + "purposeIds": [], + "legIntPurposeIds": [1, 3], + "featureIds": [] + }, + { + "id": 192, + "name": "remerge GmbH", + "policyUrl": "https://remerge.io/privacy-policy.html", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 232, + "name": "Rockerbox, Inc", + "policyUrl": "http://rockerbox.com/privacy", + "purposeIds": [], + "legIntPurposeIds": [1, 3, 5], + "featureIds": [3] + }, + { + "id": 256, + "name": "Bounce Exchange, Inc", + "policyUrl": "https://www.bouncex.com/privacy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 4, 5], + "featureIds": [1, 2] + }, + { + "id": 234, + "name": "Zebestof", + "policyUrl": "http://www.zebestof.com/en/about-us-2/privacy-en/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [5], + "featureIds": [1, 2, 3] + }, + { + "id": 246, + "name": "Smartology Limited", + "policyUrl": "https://www.smartology.net/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [5], + "featureIds": [] + }, + { + "id": 241, + "name": "OneTag Ltd", + "policyUrl": "https://www.onetag.net/privacy/", + "purposeIds": [1, 2, 3, 4], + "legIntPurposeIds": [5], + "featureIds": [1, 2, 3] + }, + { + "id": 254, + "name": "LiquidM Technology GmbH", + "policyUrl": "https://liquidm.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [3] + }, + { + "id": 215, + "name": "ARMIS SAS", + "policyUrl": "http://armis.tech/infos-cookies/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [3] + }, + { + "id": 167, + "name": "Audiens S.r.l.", + "policyUrl": "http://www.audiens.com/privacy", + "purposeIds": [1, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 240, + "name": "7Hops.com Inc. (ZergNet)", + "policyUrl": "zergnet.com/privacy", + "purposeIds": [], + "legIntPurposeIds": [1, 4, 5], + "featureIds": [] + }, + { + "id": 235, + "name": "Bucksense Inc", + "policyUrl": "http://www.bucksense.com/platform-privacy-policy/", + "purposeIds": [1], + "legIntPurposeIds": [2, 3, 4, 5], + "featureIds": [2, 3] + }, + { + "id": 185, + "name": "Bidtellect, Inc", + "policyUrl": "https://www.bidtellect.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2] + }, + { + "id": 258, + "name": "Adello Group AG", + "policyUrl": "https://www.adello.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [3] + }, + { + "id": 169, + "name": "RTK.IO, Inc", + "policyUrl": "http://www.rtk.io/privacy.html", + "purposeIds": [1, 4], + "legIntPurposeIds": [2, 3, 5], + "featureIds": [1, 3] + }, + { + "id": 208, + "name": "Spotad", + "policyUrl": "http://www.spotad.co/privacy-policy/", + "purposeIds": [2, 3], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 211, + "name": "AdTheorent, LLC", + "policyUrl": "http://adtheorent.com/privacy-policy", + "purposeIds": [], + "legIntPurposeIds": [1], + "featureIds": [] + }, + { + "id": 229, + "name": "Digitize New Media Ltd", + "policyUrl": "http://www.digitize.ie/online-privacy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [] + }, + { + "id": 273, + "name": "Bannerflow AB", + "policyUrl": "bannerflow.com/privacy", + "purposeIds": [], + "legIntPurposeIds": [1, 3], + "featureIds": [] + }, + { + "id": 104, + "name": "Sonobi, Inc", + "policyUrl": "http://sonobi.com/privacy-policy/", + "purposeIds": [1, 2], + "legIntPurposeIds": [3], + "featureIds": [1, 2, 3] + }, + { + "id": 162, + "name": "Unruly Group Ltd", + "policyUrl": "https://unruly.co/privacy/", + "purposeIds": [2], + "legIntPurposeIds": [1, 3, 5], + "featureIds": [] + }, + { + "id": 249, + "name": "Spolecznosci Sp. z o.o. Sp. k.", + "policyUrl": "https://www.spolecznosci.pl/polityka-prywatnosci", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [2, 3] + }, + { + "id": 113, + "name": "iotec global Ltd.", + "policyUrl": "https://www.iotecglobal.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 5], + "featureIds": [] + }, + { + "id": 125, + "name": "Research Now Group, Inc", + "policyUrl": "https://www.valuedopinions.co.uk/privacy", + "purposeIds": [1, 3, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 170, + "name": "Goodway Group, Inc.", + "policyUrl": "https://goodwaygroup.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 160, + "name": "Netsprint SA", + "policyUrl": "http://spoldzielnia.nsaudience.pl/opt-out/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 189, + "name": "Intowow Innovation Ltd.", + "policyUrl": "http://www.intowow.com/privacy/", + "purposeIds": [1, 3], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 279, + "name": "Mirando GmbH & Co KG", + "policyUrl": "https://wwwmirando.de/datenschutz/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 269, + "name": "Sanoma Media Finland", + "policyUrl": "https://sanoma.fi/tietoa-meista/tietosuoja/", + "purposeIds": [1, 2, 3], + "legIntPurposeIds": [4, 5], + "featureIds": [1, 2, 3] + }, + { + "id": 276, + "name": "Viralize SRL", + "policyUrl": "https://viralize.com/privacy-policy", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [3] + }, + { + "id": 87, + "name": "Genius Sports Media Limited", + "policyUrl": "http://www.geniussports.com/privacy-policy/", + "purposeIds": [2, 4], + "legIntPurposeIds": [1, 3, 5], + "featureIds": [2, 3] + }, + { + "id": 182, + "name": "Collective, Inc. dba Visto", + "policyUrl": "https://www.vistohub.com/privacy-policy/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [1, 2, 3] + }, + { + "id": 255, + "name": "Onnetwork Sp. z o.o.", + "policyUrl": "https://www.onnetwork.tv/pp_services.php", + "purposeIds": [2, 3, 5], + "legIntPurposeIds": [1], + "featureIds": [] + }, + { + "id": 203, + "name": "Revcontent, LLC", + "policyUrl": + "https://faq.revcontent.com/customer/en/portal/articles/2703838-revcontent-s-privacy-and-cookie-policy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [] + }, + { + "id": 260, + "name": "RockYou, Inc.", + "policyUrl": "https://rockyou.com/privacy-policy/", + "purposeIds": [3], + "legIntPurposeIds": [1, 2, 5], + "featureIds": [3] + }, + { + "id": 237, + "name": "LKQD, a division of Nexstar Digital, LLC.", + "policyUrl": "http://www.lkqd.com/privacy-policy/", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [2, 3] + }, + { + "id": 274, + "name": "Golden Bees", + "policyUrl": "http://goldenbees.fr/notre-politique-de-confidentialite/", + "purposeIds": [1, 2, 3, 4, 5], + "legIntPurposeIds": [], + "featureIds": [] + }, + { + "id": 280, + "name": "Spot.IM Ltd.", + "policyUrl": "http://spot.im/privacy", + "purposeIds": [], + "legIntPurposeIds": [1, 2, 3, 4, 5], + "featureIds": [] + } + ] +} diff --git a/src/assets/quiz.pubvendors.json b/src/assets/quiz.pubvendors.json new file mode 100644 index 00000000..c51853aa --- /dev/null +++ b/src/assets/quiz.pubvendors.json @@ -0,0 +1,20 @@ +{ + "publisherVendorsVersion": 1, + "globalVendorListVersion": 27, + "lastUpdated": "2018-05-23T14:03:08Z", + "vendors": [{ + "id": 10 + }, { + "id": 52 + }, { + "id": 69 + }, { + "id": 13 + }, { + "id": 32 + }, { + "id": 25 + }, { + "id": 36 + }] +} \ No newline at end of file diff --git a/src/components/app.less b/src/components/app.less index a1aa096c..3f416883 100644 --- a/src/components/app.less +++ b/src/components/app.less @@ -39,5 +39,3 @@ height: 28px; } } - - diff --git a/src/components/button/button.less b/src/components/button/button.less index d7f5a65b..5b638deb 100644 --- a/src/components/button/button.less +++ b/src/components/button/button.less @@ -9,6 +9,8 @@ button, input[type=button] { color: white; border: none; border-radius: 4px; + font-weight: bold; + text-transform: uppercase; font-size: 18px; @media @smartphone { diff --git a/src/components/popup/popup.jsx b/src/components/popup/popup.jsx index 501ede56..19ef577c 100644 --- a/src/components/popup/popup.jsx +++ b/src/components/popup/popup.jsx @@ -5,12 +5,30 @@ import CloseButton from '../closebutton/closebutton'; export default class Popup extends Component { + componentDidMount = () => { + document.onkeydown = this.onKeyDown; + } + + componentWillUnmount = () => { + document.onkeydown = null; + } + + onKeyDown = (evt) => { + evt = evt || window.event; + const {key = '', keyCode = ''} = evt; + const isEscape = (key === 'Escape' || key === 'Esc' || keyCode === 27); + if (isEscape) { + this.handleClose(); + } + } + handleClose = () => { const {store} = this.props; - store.toggleModalShowing(false) + store.toggleModalShowing(false); }; - render(props, state) { + + render(props) { const {store, onSave, theme} = props; const {overlayBackground, secondaryColor, backgroundColor} = theme; const {isModalShowing} = store; diff --git a/src/components/popup/popup.less b/src/components/popup/popup.less index 960baadf..e680a426 100644 --- a/src/components/popup/popup.less +++ b/src/components/popup/popup.less @@ -21,15 +21,16 @@ } .content { - width: 650px; - height: 700px; + width: 700px; + max-width: 90%; + height: 500px; background: white; display: flex; align-items: center; position: relative; @media @smartphone { - width: 98%; - height: 98%; + width: 90%; + height: 90%; } } diff --git a/src/lib/cmp.js b/src/lib/cmp.js index 21ec6739..0963571b 100644 --- a/src/lib/cmp.js +++ b/src/lib/cmp.js @@ -18,6 +18,8 @@ export default class Cmp { this.store = store; this.processCommand.receiveMessage = this.receiveMessage; this.commandQueue = []; + + Object.assign(this, this.commands); } commands = { @@ -224,16 +226,16 @@ export default class Cmp { // Special case where we have the full CMP implementation loaded but // we still queue these commands until there is data available. This // behavior should be removed in future versions of the CMP spec - else if ( - (!this.store.persistedVendorConsentData && (command === 'getVendorConsents' || command === 'getConsentData')) || - (!this.store.persistedPublisherConsentData && command === 'getPublisherConsents')) { - log.info(`Queuing command: ${command} until consent data is available`); - this.commandQueue.push({ - command, - parameter, - callback - }); - } + // else if ( + // (!this.store.persistedVendorConsentData && (command === 'getVendorConsents' || command === 'getConsentData')) || + // (!this.store.persistedPublisherConsentData && command === 'getPublisherConsents')) { + // log.info(`Queuing command: ${command} until consent data is available`); + // this.commandQueue.push({ + // command, + // parameter, + // callback + // }); + // } else { log.info(`Proccess command: ${command}, parameter: ${parameter}`); this.commands[command](parameter, callback); diff --git a/src/lib/config.js b/src/lib/config.js index a9b55555..652ad808 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -4,6 +4,7 @@ const defaultConfig = { customPurposeListLocation: './purposes.json', globalVendorListLocation: 'https://vendorlist.consensu.org/vendorlist.json', globalConsentLocation: './portal.html', + pubVendorListLocation: null, storeConsentGlobally: false, storePublisherData: false, logging: false, diff --git a/src/lib/cookie/cookie.js b/src/lib/cookie/cookie.js index 7a6624d2..c95871d4 100644 --- a/src/lib/cookie/cookie.js +++ b/src/lib/cookie/cookie.js @@ -18,6 +18,9 @@ const PUBLISHER_CONSENT_COOKIE_MAX_AGE = 33696000; const VENDOR_CONSENT_COOKIE_NAME = 'euconsent'; const VENDOR_CONSENT_COOKIE_MAX_AGE = 33696000; +const host = (window && window.location && window.location.hostname) || ''; +const parts = host.split('.'); +const COOKIE_DOMAIN = parts.length > 1 ? `;domain=.${parts.slice(-2).join('.')}` : ''; function encodeVendorIdsToBits(maxVendorId, selectedVendorIds = new Set()) { let vendorString = ''; @@ -204,7 +207,7 @@ function readCookie(name) { function writeCookie(name, value, maxAgeSeconds, path = '/') { const maxAge = maxAgeSeconds === null ? '' : `;max-age=${maxAgeSeconds}`; - document.cookie = `${name}=${value};path=${path}${maxAge}`; + document.cookie = `${name}=${value}${COOKIE_DOMAIN};path=${path}${maxAge}`; } function readPublisherConsentCookie() { diff --git a/src/lib/init.js b/src/lib/init.js index 8077962e..92c214c9 100755 --- a/src/lib/init.js +++ b/src/lib/init.js @@ -48,11 +48,20 @@ export function init(configUpdates) { const cmp = new Cmp(store); // Expose `processCommand` as the CMP implementation - window[CMP_GLOBAL_NAME] = cmp.processCommand; + // window[CMP_GLOBAL_NAME] = cmp.processCommand; // Notify listeners that the CMP is loaded log.debug(`Successfully loaded CMP version: ${pack.version} in ${Date.now() - startTime}ms`); + cmp.isLoaded = true; + + Object.assign(window[CMP_GLOBAL_NAME], { + ...cmp.commands, + notify: cmp.notify, + isLoaded: cmp.isLoaded, + processCommand: cmp.processCommand + }); + cmp.notify('isLoaded'); // Render the UI @@ -79,5 +88,3 @@ export function init(configUpdates) { log.error('Failed to load CMP', err); }); } - - diff --git a/src/lib/vendor.js b/src/lib/vendor.js index f2bc1e42..07ecd05e 100644 --- a/src/lib/vendor.js +++ b/src/lib/vendor.js @@ -3,13 +3,15 @@ import 'whatwg-fetch'; import config from './config'; import log from './log'; -const PUB_VENDOR_LOCATION = '/.well-known/pubvendors.json'; - /** * Fetch the pubvendors.json from the local domain */ function fetchPubVendorList() { - return fetch(PUB_VENDOR_LOCATION) + const {pubVendorListLocation} = config; + if (!pubVendorListLocation) { + return Promise.resolve(); + } + return fetch(pubVendorListLocation) .then(res => res.json()) .catch(() => {}); } diff --git a/src/lib/vendor.test.js b/src/lib/vendor.test.js index c8664798..3c8b8a3d 100644 --- a/src/lib/vendor.test.js +++ b/src/lib/vendor.test.js @@ -4,6 +4,9 @@ import { expect } from 'chai'; import config from './config'; +config.pubVendorListLocation = '/.well-known/pubvendorsList.json'; + +const {pubVendorListLocation} = config; jest.mock('./portal'); const mockPortal = require('./portal'); @@ -20,7 +23,7 @@ describe('vendor', () => { it('fetchPubVendorList fetches from `.well-known` URL', (done) => { fetchPubVendorList().then(() => { - expect(window.fetch.mock.calls[0][0]).to.equal('/.well-known/pubvendors.json'); + expect(window.fetch.mock.calls[0][0]).to.equal(pubVendorListLocation); done(); }); }); diff --git a/src/s1/cmp.js b/src/s1/cmp.js new file mode 100644 index 00000000..3d653d96 --- /dev/null +++ b/src/s1/cmp.js @@ -0,0 +1,135 @@ +// __cmp('setConsentUiCallback', callback) QUANTCAST +import 'core-js/fn/array/find-index'; +import 'core-js/fn/array/filter'; +import 'core-js/fn/array/from'; +import 'core-js/fn/array/find'; +import 'core-js/fn/array/map'; +import 'core-js/fn/object/keys'; + +import cmp from './loader'; +import {init} from '../lib/init'; +import log from '../lib/log'; +import {readCookie, writeCookie} from "../lib/cookie/cookie"; + +const GDPR_OPT_IN_COOKIE = "gdpr_opt_in"; +const GDPR_OPT_IN_COOKIE_MAX_AGE = 33696000; + +const defaultConfig = { + logging: false +}; + +const initialize = (config, callback) => { + init(config, cmp).then(() => { + cmp('addEventListener', 'onSubmit', () => { + checkConsent(); + }); + checkConsent(callback); + }); +}; + +const checkHasConsentedAll = ({ purposeConsents } = {}) => { + const hasAnyPurposeDisabled = Object.keys(purposeConsents).find(key => { + return purposeConsents[key] === false; + }); + return !hasAnyPurposeDisabled; +}; + +const checkConsent = (callback = () => {}) => { + let errorMsg = ""; + if (!cmp.isLoaded) { + errorMsg = 'CMP failed to load'; + log.error(errorMsg); + handleConsentResult({ + errorMsg + }); + } else if (!window.navigator.cookieEnabled) { + errorMsg = 'Cookies are disabled. Ignoring CMP consent check'; + log.error(errorMsg); + handleConsentResult({ + errorMsg + }); + } else { + cmp('getVendorList', null, vendorList => { + cmp('getVendorConsents', null, vendorConsentData => { + handleConsentResult({ + vendorList, + vendorConsentData, + callback + }); + }); + }); + } +}; + +const handleConsentResult = ({ + vendorList = {}, + vendorConsentData = {}, + callback, + errorMsg = "" +}) => { + const hasConsentedCookie = readCookie(GDPR_OPT_IN_COOKIE); + const { vendorListVersion: listVersion } = vendorList; + const { created, vendorListVersion } = vendorConsentData; + if (!created) { + errorMsg = 'No consent data found. Show consent tool'; + } + // if (vendorListVersion !== listVersion) { + // errorMsg = `Consent found for version ${vendorListVersion}, but received vendor list version ${listVersion}. Showing consent tool`; + // } + log.debug("FIXME: Unify pubVendorVersion and globalVendorVersion", listVersion, vendorListVersion); + if (errorMsg) { + log.debug(errorMsg); + } + + // if (!listVersion) { + // errorMsg = + // 'Could not determine vendor list version. Not showing consent tool'; + // } + + if (callback && typeof callback === "function") { + // store as 1 or 0 + const hasConsented = checkHasConsentedAll(vendorConsentData); + if (created) { + writeCookie(GDPR_OPT_IN_COOKIE, hasConsented ? "1" : "0", GDPR_OPT_IN_COOKIE_MAX_AGE); + } + const consent = { + consentRequired: true, + gdprApplies: true, + hasConsented, + vendorList, + vendorConsentData, + errorMsg + }; + callback.call(this, consent); + + if (created && hasConsented !== hasConsentedCookie) { + cmp.notify('onConsentChanged', consent); + } + } +}; + +// initialize CMP +(() => { + const initIndex = cmp.commandQueue && cmp.commandQueue.findIndex(({ command }) => { + return command === 'init'; + }); + + // 1. initialize call was queued from global scope (inline cmpLoader) + if (initIndex >= 0 && cmp.commandQueue[initIndex]) { + const [{ parameter: config, callback }] = cmp.commandQueue.splice( + initIndex, + 1 + ); // remove "init" from command list because it doesn't exist + initialize(config, callback); + + // 2. initialize call never queued, so initialize with default Config + } else { + initialize(defaultConfig, result => { + const { errorMsg } = result; + if (errorMsg) { + log.debug(errorMsg); + cmp('showConsentTool'); + } + }); + } +})(); diff --git a/src/s1/loader.js b/src/s1/loader.js new file mode 100644 index 00000000..306fe5af --- /dev/null +++ b/src/s1/loader.js @@ -0,0 +1,100 @@ +/** + * creates and manages global cmp and __cmp objects on window + * cmp queues incoming requests + * once loaded, cmp invokes cmp.processCommand() + */ +(function() { + var log = function(shouldLog, msg) { + return shouldLog && window.console && window.console.log && window.console.log(msg); + }; + + var cmpLoader = (function(cmp, __cmp) { + var gdprApplies = 'gdprApplies', + logging = 'logging', + scriptSrc = 'scriptSrc'; + + return function(isModule) { + // 1. already exists, start queueing requests + if (window[cmp] && window[__cmp]) { + window[cmp] = window[__cmp]; + return window[cmp]; + } + + // 2. create global cmp / __cmp request queues + return (function( + window, + document, + script, + commandQueue, + scriptEl, + scriptParentEl + ) { + window[__cmp] = window[cmp] = + window[cmp] || + function(command, parameter, callback) { + if (window[__cmp] !== window[cmp]) { + // __cmp takes over here + window[cmp] = window[__cmp]; + return window[cmp].apply(this, arguments); + } + + // initialization is done update processCommand + if ( + window[cmp]['processCommand'] && + typeof window[cmp]['processCommand'] === 'function' + ) { + window[cmp]['processCommand'].apply(this, arguments); + } else { + (window[cmp][commandQueue] = + window[cmp][commandQueue] || []).push({ + command, + parameter, + callback + }); + // if 'init', then we need to load the seed file + if (command === 'init') { + if (scriptEl) { + return log(parameter[logging], "CMP Error: Only call init once."); + } + if (!parameter || !parameter[scriptSrc]) { + return log(parameter[logging], "CMP Error: Provide src to load CMP. cmp('init', { scriptSrc: './cmp.js'})"); + } + if (!parameter[gdprApplies]) { + if (callback && typeof callback === "function") { + callback.apply(this, [{ + hasConsented: false, + consentRequired: false, + gdprApplies: false + }]); + } + return log(parameter[logging], "CMP: gdprApplies turned off so no CMP loaded."); + } + + scriptEl = document.createElement(script); + scriptEl.async = 1; + scriptEl.src = parameter[scriptSrc]; + scriptParentEl = document.getElementsByTagName(script)[0]; + if (scriptParentEl && scriptParentEl.parentNode) { + scriptParentEl.parentNode.insertBefore(scriptEl, scriptParentEl); + } else { + document.body.appendChild(scriptEl); + } + } + } + }; + // 4. return temporay cmp command queue + return window[cmp]; + })(window, document, 'script', 'commandQueue'); + }; + })('cmp', '__cmp'); + + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = cmpLoader(true); + } else if (typeof define === 'function' && define.amd) { + define([], function() { + return cmpLoader(true); + }); + } else { + cmpLoader(false); // External script defined here for ease of use + } +})(); diff --git a/src/s1/reference.hbs b/src/s1/reference.hbs new file mode 100644 index 00000000..f7b7a8ac --- /dev/null +++ b/src/s1/reference.hbs @@ -0,0 +1,130 @@ + + + + + System1 CMP + + +

System1 CMP Reference Pa

+
+		<script src="./{{{ htmlWebpackPlugin.options.version }}}/loader.js"></script>
+		<script>
+		const config = {
+			scriptSrc: 'https://s.flocdn.com/christian-test/{{{ htmlWebpackPlugin.options.version }}}/cmp.js',
+			gdprApplies: true
+		};
+
+		function onConsentChanged(result) {
+			console.log("onConsentChanged");
+			if (document.cookie.indexOf("gdpr_opt_in=1") < 0) {
+				console.log("all:consent:failed", result);
+			} else {
+				console.log("all:consent:succeeded", result);
+			}
+		}
+
+		cmp('init', config, function (result) {
+			if (result.consentRequired) {
+				if (result.errorMsg) {
+					cmp('showConsentTool');
+					cmp('addEventListener', 'onConsentChanged', onConsentChanged);
+				} else {
+					if (document.cookie.indexOf("gdpr_opt_in=1") >= 0) {
+						console.log("cmp:init: all consent achieved", result);
+					} else {
+						console.log("cmp:init: only some consent achieved", result);
+					}
+				}
+			} else {
+				console.log("cmp:init: consent not required", result);
+			}
+		});
+		</script>
+	
+ +
+ +

Inline Loader Script

+ +
+		{{{ htmlWebpackPlugin.options.inline }}}
+	
+ + + + + + + + + + + diff --git a/src/s1/test/loader.import.test.js b/src/s1/test/loader.import.test.js new file mode 100644 index 00000000..3e55235a --- /dev/null +++ b/src/s1/test/loader.import.test.js @@ -0,0 +1,73 @@ +/* eslint-disable max-nested-callbacks */ + +import { expect } from 'chai'; +import { init } from '../../lib/init'; + +const fakeScriptSrc = "./fake-loader-src.js"; +let cmpLoader; + +describe('cmpLoader as import', () => { + beforeEach(() => { + window.fetch = jest.fn().mockImplementation(src => { + // TODO: import mocked versions of configured json dependencies + return Promise.resolve(src); + }); + cmpLoader = require("../loader"); + }); + + afterEach(() => { + window.fetch.mockRestore(); + cmpLoader = null; + global.cmp = null; + jest.resetModules(); + }); + + it('requires initialization', done => { + expect(cmpLoader).to.not.be.undefined; + expect(typeof cmpLoader).to.equal('function'); + // expect commandQueue to not exist + done(); + }); + + it('allows you to use cmpLoader as API shim to CMP', done => { + init({}, cmpLoader).then(() => { + cmpLoader('showConsentTool', null, result => { + expect(result).to.equal(true); + done(); + }); + }); + }); + + describe("cmpLoader import and scriptLoading", () => { + global.cmp = cmpLoader; + let appendChild; + + beforeEach(() => { + appendChild = window.document.body.appendChild = jest.fn(() => { + require('../cmp'); // need to require this here because there is no built version that we can script load + }); + }); + + afterEach(() => { + appendChild.mockRestore(); + jest.resetModules(); + global.cmp = null; + }); + + it('allows you to use scriptloader', done => { + global.cmp( + 'init', + { + scriptSrc: fakeScriptSrc, + gdprApplies: true + }, + () => { + global.cmp('showConsentTool', null, result => { + expect(result).to.equal(true); + done(); + }); + } + ); + }); + }); +}); diff --git a/src/s1/test/loader.inline.test.js b/src/s1/test/loader.inline.test.js new file mode 100644 index 00000000..abc5dbce --- /dev/null +++ b/src/s1/test/loader.inline.test.js @@ -0,0 +1,173 @@ +/* eslint-disable max-nested-callbacks */ +/* eslint-disable no-eval */ + +import { expect } from 'chai'; +import fs from 'fs'; +import vendorlist from '../../docs/assets/vendorlist.json'; +// import pubvendorsStub +// import vendorlistStub +const fakeScriptSrc = './fake-loader-src.js'; + +describe('cmpLoader as script tag', () => { + let appendChild; + + beforeEach(() => { + appendChild = window.document.body.appendChild = jest.fn(() => {}); + const content = fs.readFileSync('./src/s1/loader.js'); + eval(content + '; global.cmp = cmp'); + }); + + afterEach(() => { + eval('; global.cmp = null; cmp = null;'); + jest.restoreAllMocks(); + appendChild.mockRestore(); + }); + + // 1. + it('loads cmp as script tag', done => { + expect(global.cmp).to.not.be.undefined; + expect(typeof global.cmp).to.equal('function'); + done(); + }); + + it('warns when no scriptSrc provided', done => { + const log = jest.spyOn(window.console, 'log'); + + global.cmp('init', { + logging: true + }); + expect(log.mock.calls).to.have.length(1); + expect(log.mock.calls[0][0]).to.contain('Provide src'); + log.mockRestore(); + done(); + }); + + it('warns when gdprApplies is false', done => { + const log = jest.spyOn(window.console, 'log'); + global.cmp('init', { + scriptSrc: fakeScriptSrc, + gdprApplies: false, + logging: true + }); + + expect(log.mock.calls).to.have.length(1); + expect(log.mock.calls[0][0]).to.contain( + 'gdprApplies turned off so no CMP' + ); + + log.mockRestore(); + done(); + }); + + it('appends scriptSrc if gdprApplies and scriptSrc provided', done => { + const createElement = jest.spyOn(window.document, 'createElement'); + + global.cmp('init', { + scriptSrc: fakeScriptSrc, + gdprApplies: true + }); + + expect(createElement.mock.calls).to.have.length(1); + expect(createElement.mock.calls[0][0]).to.contain('script'); + expect(appendChild.mock.calls).to.have.length(1); + + createElement.mockRestore(); + done(); + }); + + it('warns on multiple init calls', done => { + const log = jest.spyOn(window.console, 'log'); + global.cmp('init', { + scriptSrc: fakeScriptSrc, + gdprApplies: true, + logging: true + }); + + global.cmp('init', { + scriptSrc: fakeScriptSrc, + gdprApplies: true, + logging: true + }); + + expect(log.mock.calls).to.have.length(1); + expect(log.mock.calls[0][0]).to.contain('Only call init once'); + + log.mockRestore(); + done(); + }); + + describe('Complete CMP loading with reimport of loader shim', () => { + let appendChild; + + beforeEach(() => { + window.fetch = jest.fn().mockImplementation(src => { + if (src === 'https://vendorlist.consensu.org/vendorlist.json') { + return Promise.resolve({ + json: () => { + return vendorlist; + } + }); + } + return Promise.resolve(src); + }); + appendChild = window.document.body.appendChild = jest.fn(() => { + require('../cmp'); // need to require this here because there is no built version that we can script load + }); + }); + + afterEach(() => { + appendChild.mockRestore(); + jest.resetModules(); + }); + + it('triggers isLoaded after loading complete CMP', done => { + global.cmp('init', { + scriptSrc: fakeScriptSrc, + gdprApplies: true + }); + + global.cmp('addEventListener', 'isLoaded', result => { + expect(result.event).to.equal('isLoaded'); + done(); + }); + }); + + it('triggers callback on init after loading complete CMP', done => { + global.cmp( + 'init', + { + scriptSrc: fakeScriptSrc, + gdprApplies: true + }, + () => { + const init = global.cmp.commandQueue.find(({ command }) => { + return command === 'init'; + }); + expect(init).to.be.undefined; + done(); + } + ); + }); + + it('triggers callback on init and isLoaded after loading complete CMP', done => { + let count = 0; + const callback = () => { + count++; + if (count === 2) { + done(); + } + }; + + global.cmp( + 'init', + { + scriptSrc: fakeScriptSrc, + gdprApplies: true + }, + callback + ); + + global.cmp('addEventListener', 'isLoaded', callback); + }); + }); +}); diff --git a/src/style/variables.less b/src/style/variables.less index cef85b6a..6c86c88a 100644 --- a/src/style/variables.less +++ b/src/style/variables.less @@ -1,10 +1,9 @@ -@color-primary: #41afbb; +@color-primary: #37a6eb; +@color-border: #eaeaea; +@color-text: #333333; @color-secondary: #7b7b7b; -@color-border:rgba(121, 121, 121, 0.65); @color-textLight: #999; -@color-text: #333333; - @color-linkColor: @color-primary; @color-linkColorHover: darken(@color-linkColor, 20%); @color-table-background: rgba(144, 144, 144, 0.16); diff --git a/webpack.config.babel.js b/webpack.config.babel.js deleted file mode 100644 index df87ee79..00000000 --- a/webpack.config.babel.js +++ /dev/null @@ -1,238 +0,0 @@ -import webpack from 'webpack'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import CopyWebpackPlugin from 'copy-webpack-plugin'; -import autoprefixer from 'autoprefixer'; -import path from 'path'; - -const ENV = process.env.NODE_ENV || 'development'; - -const CSS_MAPS = ENV !== 'production'; - -const uglifyPlugin = new webpack.optimize.UglifyJsPlugin({ - output: { - comments: false - }, - compress: { - unsafe_comps: true, - properties: true, - keep_fargs: false, - pure_getters: true, - collapse_vars: true, - unsafe: true, - warnings: false, - screw_ie8: true, - sequences: true, - dead_code: true, - drop_debugger: true, - comparisons: true, - conditionals: true, - evaluate: true, - booleans: true, - loops: true, - unused: true, - hoist_funs: true, - if_return: true, - join_vars: true, - cascade: true, - drop_console: false - } - }); - -const commonConfig = { - context: path.resolve(__dirname, 'src'), - resolve: { - extensions: ['.jsx', '.js', '.json', '.less'], - modules: [ - path.resolve(__dirname, 'src/lib'), - path.resolve(__dirname, 'node_modules'), - 'node_modules' - ], - alias: { - components: path.resolve(__dirname, 'src/components'), // used for tests - style: path.resolve(__dirname, 'src/style'), - 'react': 'preact-compat', - 'react-dom': 'preact-compat' - } - }, - - module: { - rules: [ - { - test: /\.jsx?$/, - exclude: path.resolve(__dirname, 'src'), - enforce: 'pre', - use: 'source-map-loader' - }, - { - test: /\.jsx?$/, - exclude: /node_modules/, - use: 'babel-loader' - }, - { - // Transform our own .(less|css) files with PostCSS and CSS-modules - test: /\.(less|css)$/, - include: [ - path.resolve(__dirname, 'src/components'), - path.resolve(__dirname, 'src/docs/components'), - ], - use: [ - { - loader: 'style-loader' - }, - { - loader: 'css-loader', - options: { - modules: true, - sourceMap: CSS_MAPS, - importLoaders: 1, - minimize: true, - localIdentName: '[name]_[local]--[hash:base64:5]' - } - }, - { - loader: 'postcss-loader', - options: { - sourceMap: CSS_MAPS, - plugins: () => { - autoprefixer({ browsers: ['last 2 versions'] }); - } - } - }, - { - loader: 'less-loader', - options: { sourceMap: CSS_MAPS } - } - ] - }, - { - test: /\.(less|css)$/, - include: [ - path.resolve(__dirname, 'src/docs/style'), - path.resolve(__dirname, 'node_modules/codemirror/lib/codemirror.css'), - ], - use: [ - { - loader: 'style-loader' - }, - { - loader: 'css-loader' - }, - { - loader: 'less-loader', - options: { sourceMap: CSS_MAPS } - } - ] - }, - { - test: /\.json$/, - use: 'json-loader' - }, - { - test: /\.(xml|html|txt|md)$/, - use: 'raw-loader' - }, - { - test: /\.(svg|woff2?|ttf|eot|jpe?g|png|gif)(\?.*)?$/i, - use: ENV === 'production' ? 'file-loader' : 'url-loader' - } - ] - }, - - stats: { colors: true }, - - node: { - global: true, - process: false, - Buffer: false, - __filename: false, - __dirname: false, - setImmediate: false - }, - - devtool: ENV === 'production' ? 'source-map' : 'cheap-module-eval-source-map', - - devServer: { - port: process.env.PORT || 8080, - host: 'localhost', - publicPath: '/', - contentBase: './src', - historyApiFallback: true, - disableHostCheck: true, - open: false, - openPage: 'docs/', - https: false - } -}; - -module.exports = [ - // CMP config - { - entry: { - cmp: './index.js', - 'cmp.complete': './complete.js' - }, - - output: { - path: path.resolve(__dirname, 'build'), - publicPath: './', - filename: '[name].bundle.js' - }, - ...commonConfig, - plugins: ([ - new webpack.NoEmitOnErrorsPlugin(), - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(ENV) - }), - new webpack.ProvidePlugin({ - 'Promise': 'promise-polyfill' - }), - new HtmlWebpackPlugin({ - filename: 'index.html', - template: 'index.html', - chunks: ['cmp'] - }), - ]).concat(ENV === 'production' ? uglifyPlugin : []), - }, - // Docs config - { - entry: { - 'docs': './docs/index.jsx', - 'iframeExample': './docs/iframe/iframeExample.jsx', - 'portal': './docs/assets/portal.js' - }, - - output: { - path: path.resolve(__dirname, 'build/docs'), - publicPath: './', - filename: '[name].bundle.js' - }, - ...commonConfig, - plugins: ([ - new webpack.NoEmitOnErrorsPlugin(), - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(ENV) - }), - new webpack.ProvidePlugin({ - 'Promise': 'promise-polyfill' - }), - new HtmlWebpackPlugin({ - filename: 'index.html', - template: 'docs/index.html', - chunks: ['docs'] - }), - new HtmlWebpackPlugin({ - filename: 'iframeExample.html', - template: './docs/iframe/iframeExample.html', - chunks: ['iframeExample'] - }), - new HtmlWebpackPlugin({ - filename: 'portal.html', - template: './docs/assets/portal.html', - chunks: ['portal'] - }), - new CopyWebpackPlugin([ - { from: 'docs/assets', to: '.' } - ]) - ]).concat(ENV === 'production' ? uglifyPlugin : []), - } -]; diff --git a/yarn.lock b/yarn.lock index dfd3e4f3..14148e37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -385,6 +385,10 @@ async@^2.1.2, async@^2.1.4, async@^2.4.1, async@^2.5.0: dependencies: lodash "^4.14.0" +async@~0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -415,6 +419,15 @@ autoprefixer@^7.0.1: postcss "^6.0.17" postcss-value-parser "^3.2.3" +aws-sdk@2.3.19: + version "2.3.19" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.3.19.tgz#d040df16b1880fdf231a8d48139c8956191fc00e" + dependencies: + jmespath "0.15.0" + sax "1.1.5" + xml2js "0.4.15" + xmlbuilder "2.6.2" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" @@ -982,7 +995,15 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-preset-es2015@^6.24.0: +babel-polyfill@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + +babel-preset-es2015@^6.18.0, babel-preset-es2015@^6.24.0: version "6.24.1" resolved "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" dependencies: @@ -1018,7 +1039,7 @@ babel-preset-jest@^22.2.0: babel-plugin-jest-hoist "^22.2.0" babel-plugin-syntax-object-rest-spread "^6.13.0" -babel-preset-stage-0@^6.5.0: +babel-preset-stage-0@^6.16.0, babel-preset-stage-0@^6.5.0: version "6.24.1" resolved "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz#5642d15042f91384d7e5af8bc88b1db95b039e6a" dependencies: @@ -1053,7 +1074,7 @@ babel-preset-stage-3@^6.24.1: babel-plugin-transform-exponentiation-operator "^6.24.1" babel-plugin-transform-object-rest-spread "^6.22.0" -babel-register@^6.24.0, babel-register@^6.26.0: +babel-register@^6.18.0, babel-register@^6.24.0, babel-register@^6.26.0: version "6.26.0" resolved "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" dependencies: @@ -1171,7 +1192,7 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@3.5.1, bluebird@^3.0.5, bluebird@^3.4.7, bluebird@^3.5.0: +bluebird@3.5.1, bluebird@^3.0.5, bluebird@^3.5.0: version "3.5.1" resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -1492,7 +1513,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chai@^4.0.2: +chai@^4.0.2, chai@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" dependencies: @@ -1675,7 +1696,25 @@ clone@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" -co@^4.6.0: +co-from-stream@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/co-from-stream/-/co-from-stream-0.0.0.tgz#1a5cd8ced77263946094fa39f2499a63297bcaf9" + dependencies: + co-read "0.0.1" + +co-fs-extra@^1.0.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/co-fs-extra/-/co-fs-extra-1.2.1.tgz#3b6ad77cf2614530f677b1cf62664f5ba756b722" + dependencies: + co-from-stream "~0.0.0" + fs-extra "~0.26.5" + thunkify-wrap "~1.0.4" + +co-read@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/co-read/-/co-read-0.0.1.tgz#f81b3eb8a86675fec51e3d883a7f564e873c9389" + +co@^4.5.4, co@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -1746,6 +1785,10 @@ commander@2.14.x, commander@^2.9.0, commander@~2.14.1: version "2.14.1" resolved "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" +commander@~2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -1944,6 +1987,16 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-var@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cross-var/-/cross-var-1.1.0.tgz#f0f0d4bb235d95138d1a539842d290f00db71cd6" + dependencies: + babel-preset-es2015 "^6.18.0" + babel-preset-stage-0 "^6.16.0" + babel-register "^6.18.0" + cross-spawn "^5.0.1" + exit "^0.1.2" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -2401,6 +2454,10 @@ emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" +enable@1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/enable/-/enable-1.3.2.tgz#9eba6837d16d0982b59f87d889bf754443d52931" + encodeurl@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2855,7 +2912,7 @@ fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" -fastparse@^1.1.1: +fastparse@^1.0.0, fastparse@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" @@ -3059,6 +3116,16 @@ fs-extra@5.0.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@~0.26.5: + version "0.26.7" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -3072,6 +3139,10 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" +fs@^0.0.1-security: + version "0.0.1-security" + resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4" + fsevents@^1.0.0, fsevents@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" @@ -3163,6 +3234,16 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob@^5.0.12: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -3236,7 +3317,7 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -3248,6 +3329,15 @@ handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" +handlebars-loader@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/handlebars-loader/-/handlebars-loader-1.7.0.tgz#4f750bc62c350fb922e52d8564d667887e909723" + dependencies: + async "~0.2.10" + fastparse "^1.0.0" + loader-utils "1.0.x" + object-assign "^4.1.0" + handlebars@4.0.11, handlebars@^4.0.3: version "4.0.11" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" @@ -3458,16 +3548,25 @@ html-minifier@^3.2.3: relateurl "0.2.x" uglify-js "3.3.x" -html-webpack-plugin@^2.28.0: - version "2.30.1" - resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5" +html-webpack-inline-source-plugin@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/html-webpack-inline-source-plugin/-/html-webpack-inline-source-plugin-0.0.10.tgz#89bd5f761e4f16902aa76a44476eb52831c9f7f0" + dependencies: + escape-string-regexp "^1.0.5" + slash "^1.0.0" + source-map-url "^0.4.0" + +html-webpack-plugin@^3.0.6: + version "3.2.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" dependencies: - bluebird "^3.4.7" html-minifier "^3.2.3" loader-utils "^0.2.16" lodash "^4.17.3" pretty-error "^2.0.2" + tapable "^1.0.0" toposort "^1.0.0" + util.promisify "1.0.0" htmlparser2@~3.3.0: version "3.3.0" @@ -4282,6 +4381,10 @@ jest@^22.2.1: import-local "^1.0.0" jest-cli "^22.3.0" +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + js-base64@^2.1.9: version "2.4.3" resolved "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582" @@ -4390,6 +4493,12 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -4443,6 +4552,12 @@ kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + optionalDependencies: + graceful-fs "^4.1.9" + latest-version@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -4524,6 +4639,14 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" +loader-utils@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.0.4.tgz#13f56197f1523a305891248b4c7244540848426c" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@~0.2.2: version "0.2.17" resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" @@ -4584,10 +4707,18 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" +lodash@^3.9.3: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: version "4.17.5" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" +lodash@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.5.0.tgz#19bb3f4d51278f0b8c818ed145c74ecf9fe40e6d" + loglevel@^1.4.1: version "1.6.1" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" @@ -4801,7 +4932,7 @@ mime@1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" -mime@^1.4.1, mime@^1.5.0: +mime@^1.3.4, mime@^1.4.1, mime@^1.5.0: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -4817,7 +4948,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -6118,6 +6249,10 @@ regenerate@^1.2.1: version "1.3.3" resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: version "0.11.1" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -6349,7 +6484,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1: +rimraf@2, rimraf@2.6.2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1: version "2.6.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -6384,6 +6519,20 @@ rx-lite@*, rx-lite@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" +s3-deploy@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/s3-deploy/-/s3-deploy-1.0.1.tgz#47f0fb6a677a92e7ad61dcd8713753ddfa866535" + dependencies: + aws-sdk "2.3.19" + babel-polyfill "^6.26.0" + chai "^4.1.2" + co "^4.5.4" + co-fs-extra "^1.0.1" + glob "^5.0.12" + lodash "^3.9.3" + mime "^1.3.4" + minimist "^1.1.1" + safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -6406,7 +6555,11 @@ sane@^2.0.0: optionalDependencies: fsevents "^1.1.1" -sax@^1.2.4, sax@~1.2.1: +sax@1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.5.tgz#1da50a8d00cdecd59405659f5ff85349fe773743" + +sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -6423,9 +6576,9 @@ schema-utils@^0.4.0, schema-utils@^0.4.3: ajv "^6.1.0" ajv-keywords "^3.1.0" -script-ext-html-webpack-plugin@^1.3.4: - version "1.8.8" - resolved "https://registry.npmjs.org/script-ext-html-webpack-plugin/-/script-ext-html-webpack-plugin-1.8.8.tgz#faa888a286ce746fcd06a5e0a9e39ed7b9d24f66" +script-ext-html-webpack-plugin@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/script-ext-html-webpack-plugin/-/script-ext-html-webpack-plugin-2.0.1.tgz#90ac3d77f1892ad9054c3752f0e4673607f6d9a3" dependencies: debug "^3.1.0" @@ -7001,6 +7154,10 @@ tapable@^0.2.7: version "0.2.8" resolved "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" +tapable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" + tar-pack@^3.4.0: version "3.4.1" resolved "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" @@ -7061,6 +7218,12 @@ through@^2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +thunkify-wrap@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/thunkify-wrap/-/thunkify-wrap-1.0.4.tgz#b52be548ddfefda20e00b58c6096762b43dd6880" + dependencies: + enable "1" + thunky@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371" @@ -7185,6 +7348,13 @@ ua-parser-js@^0.7.9: version "0.7.17" resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" +uglify-es@^3.3.9: + version "3.3.9" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" + dependencies: + commander "~2.13.0" + source-map "~0.6.1" + uglify-js@3.3.x: version "3.3.10" resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.10.tgz#8e47821d4cf28e14c1826a0078ba0825ed094da8" @@ -7359,9 +7529,9 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util.promisify@^1.0.0: +util.promisify@1.0.0, util.promisify@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" dependencies: define-properties "^1.1.2" object.getownpropertydescriptors "^2.0.3" @@ -7667,6 +7837,23 @@ xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" +xml2js@0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.15.tgz#95cd03ff2dd144ec28bc6273bf2b2890c581ad0c" + dependencies: + sax ">=0.6.0" + xmlbuilder ">=2.4.6" + +xmlbuilder@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.6.2.tgz#f916f6d10d45dc171b1be2e6e673fb6e0cc35d0a" + dependencies: + lodash "~3.5.0" + +xmlbuilder@>=2.4.6: + version "10.0.0" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.0.0.tgz#c64e52f8ae097fe5fd46d1c38adaade071ee1b55" + xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"