From b6e4a13e6a94c657031d2b63d41eb3ce76e5843f Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Fri, 29 Jun 2018 09:04:58 -0700 Subject: [PATCH 01/11] (refactor) partial migration --- .babelrc | 3 +- .eslintignore | 1 + CMP-LOADER.md | 229 +++++ package.json | 13 +- src/assets/pubvendors.json | 43 + src/assets/purposes.json | 1620 +++++++++++++++++++++++++++++++ src/assets/quiz.pubvendors.json | 20 + 7 files changed, 1925 insertions(+), 4 deletions(-) create mode 100644 .eslintignore create mode 100644 CMP-LOADER.md create mode 100644 src/assets/pubvendors.json create mode 100644 src/assets/purposes.json create mode 100644 src/assets/quiz.pubvendors.json 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..89fe779e --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/loader.js diff --git a/CMP-LOADER.md b/CMP-LOADER.md new file mode 100644 index 00000000..66fcf5a2 --- /dev/null +++ b/CMP-LOADER.md @@ -0,0 +1,229 @@ +# System1 CMP Loader + +The System1 CMP Loader is a shim around the [appnexus-cmp](https://github.com/appnexus/cmp); it aims to improve installation, integration, and management of the underlying CMP and to allow us to swap out the underlying CMP at a later date. + +[Complete Docs Are a Work In Progress](http://s.flocdn.com/cmp/docs/#/) + + + + + +- [Terminology](#terminology) +- [Installation](#installation) + - [Quick Start: Install With Script tag](#quick-start-install-with-script-tag) + - [Inline: Install with raw loader tag](#inline-install-with-raw-loader-tag) + - [Import CMP](#import-cmp) +- [Roadmap](#roadmap) +- [System1 CMP Loader API](#system1-cmp-loader-api) + - [Arguments](#arguments) + - [Commands](#commands) + - [Events](#events) + - [Deploy](#deploy) + + + +# Terminology + +| Term | Description | +| --- | --- | +| CMP Loader | System1 CMP loader or container. Provides API shell, loader, initializer for actual CMP | +| CMP | Consent Management Platform implementation detail and UI ([appnexus-cmp](https://github.com/appnexus/cmp), quantcast-cmp) | + +# Installation + +The CMP Loader provides a shim to the CMP SDK. Use the CMP Loader to queue commands and events asynchronously to the CMP SDK. + +## Quickstart + +``` + + + + + + +``` + +# Roadmap + +The goal is to provide a CMP loader that acts as an SDK for integrating the CMP with your website where you might need more support listening for sideEffects. + +- [x] Provide a CMP loader that maintains CMP scope after loading +- [x] Allow `import` of CMP so it can be included in another CMP project as an SDK +- [x] Allow CMP Loader to be directly inlined for immediate use. +- [x] Expose `init` function to allow for dynamic configuration +- [x] Set cookie `gdpr_opt_in` as boolean for user consent to all Purposes/Vendors or not +- [x] Allow customization of location for `pubvendors.json` +- [ ] Return consent object in onSubmit +- [ ] Update `commands` to return Promises +- [ ] Publish to NPM for import support +- [ ] Add `consentChanged` event to trigger change in consent + + +# 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)); +``` + +# Deploy + +``` +yarn deploy +``` diff --git a/package.json b/package.json index ef6429e1..555bc880 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,15 @@ { "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", "prestart": "npm run build", "build": "cross-env NODE_ENV=production webpack --progress", + "deploy": "npm run build && npm run upload && npm run invalidate", + "upload": "aws s3 cp --recursive build/ s3://s1-layout-cdn/cmp", + "invalidate": "aws cloudfront create-invalidation --distribution-id E5JQ1CRXXPTKM --paths /cmp/*", "prebuild": "npm run clean && mkdirp build", "test": "npm run -s lint && jest --coverage", "test:watch": "npm run -s test -- --watch", @@ -66,7 +69,10 @@ "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 +85,12 @@ "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", + "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 From afd905d6e933215f5e934962909f63ca14b3f4b4 Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Fri, 29 Jun 2018 12:41:23 -0700 Subject: [PATCH 02/11] (migrate) upgrade to latest cmp and move to openmail repo --- .eslintrc | 3 +- src/components/app.less | 2 - src/components/button/button.less | 4 +- src/components/closebutton/closebutton.less | 4 +- src/components/popup/popup.jsx | 21 ++- src/components/popup/popup.less | 9 +- src/lib/cmp.js | 22 +-- src/lib/cookie/cookie.js | 5 +- src/lib/init.js | 13 +- src/loader.js | 100 +++++++++++ src/s1/cmp.js | 135 +++++++++++++++ src/s1/constants.js | 31 ++++ src/s1/embed.js | 105 ++++++++++++ src/s1cmp.hbs | 88 ++++++++++ src/style/variables.less | 2 +- src/test/loader.import.test.js | 73 +++++++++ src/test/loader.inline.test.js | 173 ++++++++++++++++++++ webpack.config.babel.js | 39 +++++ yarn.lock | 71 ++++++-- 19 files changed, 862 insertions(+), 38 deletions(-) create mode 100644 src/loader.js create mode 100644 src/s1/cmp.js create mode 100644 src/s1/constants.js create mode 100644 src/s1/embed.js create mode 100644 src/s1cmp.hbs create mode 100644 src/test/loader.import.test.js create mode 100644 src/test/loader.inline.test.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/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 11bdf4b3..53bce976 100644 --- a/src/components/button/button.less +++ b/src/components/button/button.less @@ -9,7 +9,9 @@ button, input[type=button] { color: white; border: none; border-radius: 4px; - font-size: 18px; + font-size: 1em; + font-weight: bold; + text-transform: uppercase; &:hover { color: @color-linkColorHover; } diff --git a/src/components/closebutton/closebutton.less b/src/components/closebutton/closebutton.less index ec62e475..ddd41fb7 100644 --- a/src/components/closebutton/closebutton.less +++ b/src/components/closebutton/closebutton.less @@ -8,8 +8,8 @@ height: @button-size; cursor: pointer; position: absolute; - top: 5px; - right: 5px; + top: -10px; + right: -10px; &.hasBorder { background-color: white; diff --git a/src/components/popup/popup.jsx b/src/components/popup/popup.jsx index f5b85c70..84437b7f 100644 --- a/src/components/popup/popup.jsx +++ b/src/components/popup/popup.jsx @@ -5,12 +5,29 @@ import Details from './details/details'; 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} = props; 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/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 973b31b0..87406b7c 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/loader.js b/src/loader.js new file mode 100644 index 00000000..306fe5af --- /dev/null +++ b/src/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/cmp.js b/src/s1/cmp.js new file mode 100644 index 00000000..cb4f43d2 --- /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/constants.js b/src/s1/constants.js new file mode 100644 index 00000000..8868038e --- /dev/null +++ b/src/s1/constants.js @@ -0,0 +1,31 @@ +export const GDPRCountries = [ + 'AT', + 'BE', + 'BG', + 'HR', + 'CY', + 'CZ', + 'DK', + 'EE', + 'FI', + 'FR', + 'DE', + 'GR', + 'HU', + 'IE', + 'IT', + 'LV', + 'LT', + 'LU', + 'MT', + 'NL', + 'PL', + 'PT', + 'RO', + 'SK', + 'SI', + 'ES', + 'SE', + 'GB', + 'US' // @FIXME @christian remove +]; diff --git a/src/s1/embed.js b/src/s1/embed.js new file mode 100644 index 00000000..0263451b --- /dev/null +++ b/src/s1/embed.js @@ -0,0 +1,105 @@ +/** + * creates and manages global cmp and __cmp objects on window + * cmp queues incoming requests + * once loaded, cmp invokes cmp.processCommand() + */ +(function() { + const countries = [ + 'AT', + 'BE', + 'BG', + 'HR', + 'CY', + 'CZ', + 'DK', + 'EE', + 'FI', + 'FR', + 'DE', + 'GR', + 'HU', + 'IE', + 'IT', + 'LV', + 'LT', + 'LU', + 'MT', + 'NL', + 'PL', + 'PT', + 'RO', + 'SK', + 'SI', + 'ES', + 'SE', + 'GB', + 'US' // FIXME @charden remove + ]; + + let cmpLoader = (function(cmp, __cmp) { + return function(scriptSrc, countryCode) { + // 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 + }); + } + }; + + // 3. load cmp if script inlined + if (countries.indexOf(countryCode) >= 0 && scriptSrc) { + (scriptEl = document.createElement(script)), + (scriptParentEl = document.getElementsByTagName(script)[0]); + scriptEl.async = 1; + scriptEl.src = scriptSrc; + scriptParentEl.parentNode.insertBefore(scriptEl, scriptParentEl); + } + + // 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; + } else if (typeof define === 'function' && define.amd) { + define([], () => { + return cmpLoader; + }); + } else { + window.cmpLoader = cmpLoader; // External script defined here for ease of use + } +})(); diff --git a/src/s1cmp.hbs b/src/s1cmp.hbs new file mode 100644 index 00000000..ae6dbdf6 --- /dev/null +++ b/src/s1cmp.hbs @@ -0,0 +1,88 @@ + + + + + System1 CMP + + + + + + + + + + + + + diff --git a/src/style/variables.less b/src/style/variables.less index 636dcba8..de88b414 100644 --- a/src/style/variables.less +++ b/src/style/variables.less @@ -1,4 +1,4 @@ -@color-primary: #41afbb; +@color-primary: #37a6eb; @color-border: #eaeaea; @color-textLight: #8a8a8a; @color-text: #333333; diff --git a/src/test/loader.import.test.js b/src/test/loader.import.test.js new file mode 100644 index 00000000..155fb6b9 --- /dev/null +++ b/src/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('../s1/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/test/loader.inline.test.js b/src/test/loader.inline.test.js new file mode 100644 index 00000000..7385b4aa --- /dev/null +++ b/src/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/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('../s1/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/webpack.config.babel.js b/webpack.config.babel.js index df87ee79..5f16a4c0 100644 --- a/webpack.config.babel.js +++ b/webpack.config.babel.js @@ -57,6 +57,11 @@ const commonConfig = { module: { rules: [ + { + test: /\.hbs/, + exclude: /node_modules/, + use: 'handlebars-loader' + }, { test: /\.jsx?$/, exclude: path.resolve(__dirname, 'src'), @@ -165,6 +170,40 @@ const commonConfig = { }; module.exports = [ + // S1 CMP + { + entry: { + cmp: './s1/cmp.js' + }, + ...commonConfig, + output: { + path: path.resolve(__dirname, 'build'), + publicPath: './', + filename: 's1.[name].js' + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new HtmlWebpackPlugin({ + filename: 's1cmp.html', + template: 's1cmp.hbs', + inject: false, + inline: UglifyJS.minify(fs.readFileSync('./src/loader.js', 'utf8')).code + }), + new CopyWebpackPlugin([ + { from: 'assets', to: '.' }, + { + from: 'loader.js', + to: '.', + 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 : []) + }, // CMP config { entry: { diff --git a/yarn.lock b/yarn.lock index dfd3e4f3..c62a5791 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" @@ -1171,7 +1175,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" @@ -1746,6 +1750,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" @@ -2855,7 +2863,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" @@ -3072,6 +3080,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" @@ -3248,6 +3260,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 +3479,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" @@ -4524,6 +4554,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" @@ -6423,9 +6461,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 +7039,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" @@ -7185,6 +7227,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 +7408,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" From 60cc951d36deae6f8de6ba7eb13a1d9972bc91c4 Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Fri, 29 Jun 2018 13:09:59 -0700 Subject: [PATCH 03/11] (refactor) fix webpack dev server for s1 --- webpack.config.babel.js | 94 +++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/webpack.config.babel.js b/webpack.config.babel.js index 5f16a4c0..c588d7fb 100644 --- a/webpack.config.babel.js +++ b/webpack.config.babel.js @@ -3,40 +3,42 @@ import HtmlWebpackPlugin from 'html-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import autoprefixer from 'autoprefixer'; import path from 'path'; +import fs from 'fs'; +import UglifyJS from 'uglify-es'; 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 - } - }); + 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'), @@ -48,9 +50,9 @@ const commonConfig = { 'node_modules' ], alias: { - components: path.resolve(__dirname, 'src/components'), // used for tests + components: path.resolve(__dirname, 'src/components'), // used for tests style: path.resolve(__dirname, 'src/style'), - 'react': 'preact-compat', + react: 'preact-compat', 'react-dom': 'preact-compat' } }, @@ -78,7 +80,7 @@ const commonConfig = { test: /\.(less|css)$/, include: [ path.resolve(__dirname, 'src/components'), - path.resolve(__dirname, 'src/docs/components'), + path.resolve(__dirname, 'src/docs/components') ], use: [ { @@ -113,7 +115,7 @@ const commonConfig = { test: /\.(less|css)$/, include: [ path.resolve(__dirname, 'src/docs/style'), - path.resolve(__dirname, 'node_modules/codemirror/lib/codemirror.css'), + path.resolve(__dirname, 'node_modules/codemirror/lib/codemirror.css') ], use: [ { @@ -198,7 +200,9 @@ module.exports = [ to: '.', transform(content) { // Just want to uglify and copy this file over - return Promise.resolve(Buffer.from(UglifyJS.minify(content.toString()).code, 'utf8')); + return Promise.resolve( + Buffer.from(UglifyJS.minify(content.toString()).code, 'utf8') + ); } } ]) @@ -217,27 +221,27 @@ module.exports = [ filename: '[name].bundle.js' }, ...commonConfig, - plugins: ([ + plugins: [ new webpack.NoEmitOnErrorsPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(ENV) }), new webpack.ProvidePlugin({ - 'Promise': 'promise-polyfill' + Promise: 'promise-polyfill' }), new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', chunks: ['cmp'] - }), - ]).concat(ENV === 'production' ? uglifyPlugin : []), + }) + ].concat(ENV === 'production' ? uglifyPlugin : []) }, // Docs config { entry: { - 'docs': './docs/index.jsx', - 'iframeExample': './docs/iframe/iframeExample.jsx', - 'portal': './docs/assets/portal.js' + docs: './docs/index.jsx', + iframeExample: './docs/iframe/iframeExample.jsx', + portal: './docs/assets/portal.js' }, output: { @@ -246,13 +250,13 @@ module.exports = [ filename: '[name].bundle.js' }, ...commonConfig, - plugins: ([ + plugins: [ new webpack.NoEmitOnErrorsPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(ENV) }), new webpack.ProvidePlugin({ - 'Promise': 'promise-polyfill' + Promise: 'promise-polyfill' }), new HtmlWebpackPlugin({ filename: 'index.html', @@ -269,9 +273,7 @@ module.exports = [ template: './docs/assets/portal.html', chunks: ['portal'] }), - new CopyWebpackPlugin([ - { from: 'docs/assets', to: '.' } - ]) - ]).concat(ENV === 'production' ? uglifyPlugin : []), + new CopyWebpackPlugin([{ from: 'docs/assets', to: '.' }]) + ].concat(ENV === 'production' ? uglifyPlugin : []) } ]; From 7ac2e220c21cdf2ecad6f9038a52807624a08c88 Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Fri, 29 Jun 2018 13:20:37 -0700 Subject: [PATCH 04/11] (refactor) add cross-var and update deploy:s1 npm scripts --- .gitignore | 1 + package.json | 8 +++++--- yarn.lock | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) 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/package.json b/package.json index 555bc880..6c69e203 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,15 @@ { "name": "appnexus-cmp", - "version": "0.0.1", + "version": "0.1.0", "scripts": { "clean": "rm -rf build", "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress", "start": "serve build -s -c 1", "prestart": "npm run build", "build": "cross-env NODE_ENV=production webpack --progress", - "deploy": "npm run build && npm run upload && npm run invalidate", - "upload": "aws s3 cp --recursive build/ s3://s1-layout-cdn/cmp", + "build:s1": "rimraf ./dist && 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/christian-test/$npm_package_version --gzip --preventUpdates --immutable --ext js", "invalidate": "aws cloudfront create-invalidation --distribution-id E5JQ1CRXXPTKM --paths /cmp/*", "prebuild": "npm run clean && mkdirp build", "test": "npm run -s lint && jest --coverage", @@ -99,6 +100,7 @@ "classnames": "^2.2.5", "codemirror": "^5.34.0", "core-js": "^2.5.3", + "cross-var": "^1.1.0", "js-beautify": "^1.7.5", "lodash": "^4.17.4", "preact": "^8.1.0", diff --git a/yarn.lock b/yarn.lock index c62a5791..bf1e7391 100644 --- a/yarn.lock +++ b/yarn.lock @@ -986,7 +986,7 @@ 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-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: @@ -1022,7 +1022,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: @@ -1057,7 +1057,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: @@ -1952,6 +1952,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" From 4987361b61c54e6422f855927c619e0e80b9265b Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Fri, 29 Jun 2018 14:15:35 -0700 Subject: [PATCH 05/11] (feat) deploy:s1 --- config/common.webpack.config.babel.js | 158 ++++++++++++++ config/s1.webpack.config.babel.js | 40 ++++ config/webpack.config.babel.js | 293 ++++++++++++++++++++++++++ package.json | 6 +- yarn.lock | 142 ++++++++++++- 5 files changed, 630 insertions(+), 9 deletions(-) create mode 100644 config/common.webpack.config.babel.js create mode 100644 config/s1.webpack.config.babel.js create mode 100644 config/webpack.config.babel.js diff --git a/config/common.webpack.config.babel.js b/config/common.webpack.config.babel.js new file mode 100644 index 00000000..afd30aa1 --- /dev/null +++ b/config/common.webpack.config.babel.js @@ -0,0 +1,158 @@ +/* @noflow */ +/* eslint-disable import/no-extraneous-dependencies */ + +import webpack from 'webpack'; +// import CopyWebpackPlugin from 'copy-webpack-plugin'; +import autoprefixer from 'autoprefixer'; +import path from 'path'; +// import UglifyJS from 'uglify-es'; +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..724b810d --- /dev/null +++ b/config/s1.webpack.config.babel.js @@ -0,0 +1,40 @@ +/* @noflow */ +/* eslint-disable import/no-extraneous-dependencies */ +import webpack from 'webpack'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import path from 'path'; +import UglifyJS from 'uglify-es'; + +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: 'sdk.js' + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new CopyWebpackPlugin([ + { + from: 'loader.js', + to: './shim.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..615c8a7c --- /dev/null +++ b/config/webpack.config.babel.js @@ -0,0 +1,293 @@ +/* @noflow */ +/* eslint-disable import/no-extraneous-dependencies */ + +import webpack from 'webpack'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import autoprefixer from 'autoprefixer'; +import path from 'path'; +import fs from 'fs'; +import UglifyJS from 'uglify-es'; + +import packageJson from '../package.json'; + +const {version} = packageJson; + +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: /\.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 + }, + + 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 = [ + // // S1 Loader + // { + // context: path.resolve(__dirname, 'src'), + // entry: './loader.js', + // output: { + // path: path.resolve(__dirname, 'build'), + // publicPath: './', + // filename: 'loader.js' + // }, + // plugins: [ + // new webpack.optimize.UglifyJsPlugin({ + // minimize: true, + // compress: { warnings: false } + // }) + // ] + // }, + // S1 CMP + { + entry: { + cmp: './s1/cmp.js' + }, + ...commonConfig, + output: { + path: path.resolve(__dirname, `build/${version}`), + publicPath: './', + filename: 's1.[name].js' + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new HtmlWebpackPlugin({ + filename: 's1cmp.html', + template: 's1cmp.hbs', + inject: false, + inline: UglifyJS.minify(fs.readFileSync('./src/loader.js', 'utf8')).code + }), + new CopyWebpackPlugin([ + {from: 'assets', to: '.'}, + { + from: 'loader.js', + to: '.', + 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 : []) + }, + // 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/package.json b/package.json index 6c69e203..3e3c9677 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "appnexus-cmp", - "version": "0.1.0", + "version": "0.0.3", "scripts": { "clean": "rm -rf build", "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress", @@ -64,6 +64,7 @@ "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", @@ -86,6 +87,8 @@ "raw-loader": "^1.0.0-beta.0", "regenerator-runtime": "^0.11.1", "replace-bundle-webpack-plugin": "^1.0.0", + "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", @@ -100,7 +103,6 @@ "classnames": "^2.2.5", "codemirror": "^5.34.0", "core-js": "^2.5.3", - "cross-var": "^1.1.0", "js-beautify": "^1.7.5", "lodash": "^4.17.4", "preact": "^8.1.0", diff --git a/yarn.lock b/yarn.lock index bf1e7391..14148e37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -419,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" @@ -986,6 +995,14 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" +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" @@ -1496,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: @@ -1679,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" @@ -2419,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" @@ -3077,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" @@ -3185,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" @@ -3258,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" @@ -4322,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" @@ -4430,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" @@ -4483,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" @@ -4632,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" @@ -4849,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" @@ -4865,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: @@ -6166,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" @@ -6397,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: @@ -6432,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" @@ -6454,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" @@ -7113,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" @@ -7726,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" From 023c6c9f60d82428a2140bfb6908bdd3cdbfc2e3 Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Fri, 29 Jun 2018 14:30:28 -0700 Subject: [PATCH 06/11] (refactor) move to configure dir --- config/common.webpack.config.babel.js | 279 ++++++++++--------- config/s1.webpack.config.babel.js | 78 ++++-- config/webpack.config.babel.js | 383 +++++++------------------- package.json | 5 +- webpack.config.babel.js | 279 ------------------- 5 files changed, 302 insertions(+), 722 deletions(-) delete mode 100644 webpack.config.babel.js diff --git a/config/common.webpack.config.babel.js b/config/common.webpack.config.babel.js index afd30aa1..0ab05955 100644 --- a/config/common.webpack.config.babel.js +++ b/config/common.webpack.config.babel.js @@ -10,149 +10,156 @@ import packageJson from '../package.json'; const ENV = process.env.NODE_ENV || 'development'; const CSS_MAPS = ENV !== 'production'; -const {version} = packageJson; +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 - } + 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' - } - }, + 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' - } - ] - }, + 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}, + stats: { colors: true }, - node: { - global: true, - process: false, - Buffer: false, - __filename: false, - __dirname: false, - setImmediate: false - } + node: { + global: true, + process: false, + Buffer: false, + __filename: false, + __dirname: false, + setImmediate: false + } }; -export {commonConfig, ENV, CSS_MAPS, version, uglifyPlugin}; +export { commonConfig, ENV, CSS_MAPS, version, uglifyPlugin }; diff --git a/config/s1.webpack.config.babel.js b/config/s1.webpack.config.babel.js index 724b810d..1a3e54e0 100644 --- a/config/s1.webpack.config.babel.js +++ b/config/s1.webpack.config.babel.js @@ -5,36 +5,58 @@ import CopyWebpackPlugin from 'copy-webpack-plugin'; import path from 'path'; import UglifyJS from 'uglify-es'; -import {commonConfig, uglifyPlugin, version} from './common.webpack.config.babel'; +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: 'sdk.js' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(ENV) - }), - new CopyWebpackPlugin([ - { - from: 'loader.js', - to: './shim.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 : []) - } + // // S1 Loader + // { + // context: path.resolve(__dirname, '../', 'src'), + // entry: './loader.js', + // output: { + // path: path.resolve(__dirname, '../', 'build'), + // publicPath: './', + // filename: 'loader.js' + // }, + // plugins: [ + // new webpack.optimize.UglifyJsPlugin({ + // minimize: true, + // compress: { warnings: false } + // }) + // ] + // }, + // S1 CMP + { + entry: { + cmp: './s1/cmp.js' + }, + ...commonConfig, + output: { + path: path.resolve(__dirname, '../', `dist/${version}`), + publicPath: './', + filename: 'sdk.js' + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new CopyWebpackPlugin([ + { + from: 'loader.js', + to: './shim.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 index 615c8a7c..657f3abd 100644 --- a/config/webpack.config.babel.js +++ b/config/webpack.config.babel.js @@ -4,290 +4,121 @@ import webpack from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; -import autoprefixer from 'autoprefixer'; import path from 'path'; import fs from 'fs'; import UglifyJS from 'uglify-es'; - -import packageJson from '../package.json'; - -const {version} = packageJson; +import { + commonConfig, + uglifyPlugin, + version +} from './common.webpack.config.babel'; 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: /\.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 - }, - - 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 = [ - // // S1 Loader - // { - // context: path.resolve(__dirname, 'src'), - // entry: './loader.js', - // output: { - // path: path.resolve(__dirname, 'build'), - // publicPath: './', - // filename: 'loader.js' - // }, - // plugins: [ - // new webpack.optimize.UglifyJsPlugin({ - // minimize: true, - // compress: { warnings: false } - // }) - // ] - // }, - // S1 CMP - { - entry: { - cmp: './s1/cmp.js' - }, - ...commonConfig, - output: { - path: path.resolve(__dirname, `build/${version}`), - publicPath: './', - filename: 's1.[name].js' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(ENV) - }), - new HtmlWebpackPlugin({ - filename: 's1cmp.html', - template: 's1cmp.hbs', - inject: false, - inline: UglifyJS.minify(fs.readFileSync('./src/loader.js', 'utf8')).code - }), - new CopyWebpackPlugin([ - {from: 'assets', to: '.'}, - { - from: 'loader.js', - to: '.', - 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 : []) - }, - // CMP config - { - entry: { - cmp: './index.js', - 'cmp.complete': './complete.js' - }, + // S1 CMP + { + entry: { + cmp: './s1/cmp.js' + }, + ...commonConfig, + output: { + path: path.resolve(__dirname, '../', `build/${version}`), + publicPath: './', + filename: 's1.[name].js' + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(ENV) + }), + new HtmlWebpackPlugin({ + filename: 's1cmp.html', + template: 's1cmp.hbs', + inject: false, + inline: UglifyJS.minify(fs.readFileSync('./src/loader.js', 'utf8')).code + }), + new CopyWebpackPlugin([ + { from: 'assets', to: '.' }, + { + from: 'loader.js', + to: '.', + 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 : []) + }, + // 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'), + 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 : []) - } + 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/package.json b/package.json index 3e3c9677..2c36c3ef 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,13 @@ "version": "0.0.3", "scripts": { "clean": "rm -rf build", - "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress", + "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress --config config/webpack.config.babel.js", "start": "serve build -s -c 1", "prestart": "npm run build", - "build": "cross-env NODE_ENV=production webpack --progress", + "build": "cross-env NODE_ENV=production webpack --progress --config config/webpack.config.babel.js", "build:s1": "rimraf ./dist && 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/christian-test/$npm_package_version --gzip --preventUpdates --immutable --ext js", - "invalidate": "aws cloudfront create-invalidation --distribution-id E5JQ1CRXXPTKM --paths /cmp/*", "prebuild": "npm run clean && mkdirp build", "test": "npm run -s lint && jest --coverage", "test:watch": "npm run -s test -- --watch", diff --git a/webpack.config.babel.js b/webpack.config.babel.js deleted file mode 100644 index c588d7fb..00000000 --- a/webpack.config.babel.js +++ /dev/null @@ -1,279 +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'; -import fs from 'fs'; -import UglifyJS from 'uglify-es'; - -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: /\.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 - }, - - 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 = [ - // S1 CMP - { - entry: { - cmp: './s1/cmp.js' - }, - ...commonConfig, - output: { - path: path.resolve(__dirname, 'build'), - publicPath: './', - filename: 's1.[name].js' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(ENV) - }), - new HtmlWebpackPlugin({ - filename: 's1cmp.html', - template: 's1cmp.hbs', - inject: false, - inline: UglifyJS.minify(fs.readFileSync('./src/loader.js', 'utf8')).code - }), - new CopyWebpackPlugin([ - { from: 'assets', to: '.' }, - { - from: 'loader.js', - to: '.', - 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 : []) - }, - // 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 : []) - } -]; From 5054fb103cc9dfc8109ccea91bbeb36d6bfd30ac Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Mon, 2 Jul 2018 18:34:45 -0700 Subject: [PATCH 07/11] (refactor) reorganize files --- config/s1.webpack.config.babel.js | 29 ++++++++----------- config/webpack.config.babel.js | 43 ++--------------------------- src/lib/config.js | 1 + src/lib/vendor.js | 6 +++- src/lib/vendor.test.js | 5 +++- src/{s1cmp.hbs => s1/reference.hbs} | 4 +-- 6 files changed, 25 insertions(+), 63 deletions(-) rename src/{s1cmp.hbs => s1/reference.hbs} (90%) diff --git a/config/s1.webpack.config.babel.js b/config/s1.webpack.config.babel.js index 1a3e54e0..6761cb19 100644 --- a/config/s1.webpack.config.babel.js +++ b/config/s1.webpack.config.babel.js @@ -3,7 +3,9 @@ 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, @@ -14,22 +16,6 @@ import { const ENV = process.env.NODE_ENV || 'development'; module.exports = [ - // // S1 Loader - // { - // context: path.resolve(__dirname, '../', 'src'), - // entry: './loader.js', - // output: { - // path: path.resolve(__dirname, '../', 'build'), - // publicPath: './', - // filename: 'loader.js' - // }, - // plugins: [ - // new webpack.optimize.UglifyJsPlugin({ - // minimize: true, - // compress: { warnings: false } - // }) - // ] - // }, // S1 CMP { entry: { @@ -39,16 +25,23 @@ module.exports = [ output: { path: path.resolve(__dirname, '../', `dist/${version}`), publicPath: './', - filename: 'sdk.js' + 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/loader.js', 'utf8')).code, + version + }), new CopyWebpackPlugin([ { from: 'loader.js', - to: './shim.js', + to: './loader.js', transform(content) { // Just want to uglify and copy this file over return Promise.resolve( diff --git a/config/webpack.config.babel.js b/config/webpack.config.babel.js index 657f3abd..111ee9f1 100644 --- a/config/webpack.config.babel.js +++ b/config/webpack.config.babel.js @@ -5,55 +5,16 @@ import webpack from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import path from 'path'; -import fs from 'fs'; -import UglifyJS from 'uglify-es'; import { commonConfig, - uglifyPlugin, - version + uglifyPlugin } 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, '../', `build/${version}`), - publicPath: './', - filename: 's1.[name].js' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(ENV) - }), - new HtmlWebpackPlugin({ - filename: 's1cmp.html', - template: 's1cmp.hbs', - inject: false, - inline: UglifyJS.minify(fs.readFileSync('./src/loader.js', 'utf8')).code - }), - new CopyWebpackPlugin([ - { from: 'assets', to: '.' }, - { - from: 'loader.js', - to: '.', - 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 : []) - }, - // CMP config + // CMP { entry: { cmp: './index.js', 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/vendor.js b/src/lib/vendor.js index f2bc1e42..ef0ab774 100644 --- a/src/lib/vendor.js +++ b/src/lib/vendor.js @@ -9,7 +9,11 @@ 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/s1cmp.hbs b/src/s1/reference.hbs similarity index 90% rename from src/s1cmp.hbs rename to src/s1/reference.hbs index ae6dbdf6..44fd440a 100644 --- a/src/s1cmp.hbs +++ b/src/s1/reference.hbs @@ -10,10 +10,10 @@ --> - + From faa5e2251c1d47adb613899f4a97ebc1c33b9f06 Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Tue, 3 Jul 2018 12:18:52 -0700 Subject: [PATCH 09/11] (refactor) reogranize loader files and update deploy --- .eslintignore | 2 +- CHANGELOG.md | 8 + CMP-LOADER.md | 229 ---------------------- README.md | 241 ++++++++++++++++++++++++ config/s1.webpack.config.babel.js | 4 +- package.json | 8 +- src/lib/vendor.js | 2 - src/s1/cmp.js | 2 +- src/s1/constants.js | 31 --- src/s1/embed.js | 105 ----------- src/{ => s1}/loader.js | 0 src/{ => s1}/test/loader.import.test.js | 4 +- src/{ => s1}/test/loader.inline.test.js | 6 +- 13 files changed, 262 insertions(+), 380 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 CMP-LOADER.md delete mode 100644 src/s1/constants.js delete mode 100644 src/s1/embed.js rename src/{ => s1}/loader.js (100%) rename src/{ => s1}/test/loader.import.test.js (91%) rename src/{ => s1}/test/loader.inline.test.js (94%) diff --git a/.eslintignore b/.eslintignore index 89fe779e..ff866096 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1 @@ -src/loader.js +src/s1/loader.js 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/CMP-LOADER.md b/CMP-LOADER.md deleted file mode 100644 index 66fcf5a2..00000000 --- a/CMP-LOADER.md +++ /dev/null @@ -1,229 +0,0 @@ -# System1 CMP Loader - -The System1 CMP Loader is a shim around the [appnexus-cmp](https://github.com/appnexus/cmp); it aims to improve installation, integration, and management of the underlying CMP and to allow us to swap out the underlying CMP at a later date. - -[Complete Docs Are a Work In Progress](http://s.flocdn.com/cmp/docs/#/) - - - - - -- [Terminology](#terminology) -- [Installation](#installation) - - [Quick Start: Install With Script tag](#quick-start-install-with-script-tag) - - [Inline: Install with raw loader tag](#inline-install-with-raw-loader-tag) - - [Import CMP](#import-cmp) -- [Roadmap](#roadmap) -- [System1 CMP Loader API](#system1-cmp-loader-api) - - [Arguments](#arguments) - - [Commands](#commands) - - [Events](#events) - - [Deploy](#deploy) - - - -# Terminology - -| Term | Description | -| --- | --- | -| CMP Loader | System1 CMP loader or container. Provides API shell, loader, initializer for actual CMP | -| CMP | Consent Management Platform implementation detail and UI ([appnexus-cmp](https://github.com/appnexus/cmp), quantcast-cmp) | - -# Installation - -The CMP Loader provides a shim to the CMP SDK. Use the CMP Loader to queue commands and events asynchronously to the CMP SDK. - -## Quickstart - -``` - - - - - - -``` - -# Roadmap - -The goal is to provide a CMP loader that acts as an SDK for integrating the CMP with your website where you might need more support listening for sideEffects. - -- [x] Provide a CMP loader that maintains CMP scope after loading -- [x] Allow `import` of CMP so it can be included in another CMP project as an SDK -- [x] Allow CMP Loader to be directly inlined for immediate use. -- [x] Expose `init` function to allow for dynamic configuration -- [x] Set cookie `gdpr_opt_in` as boolean for user consent to all Purposes/Vendors or not -- [x] Allow customization of location for `pubvendors.json` -- [ ] Return consent object in onSubmit -- [ ] Update `commands` to return Promises -- [ ] Publish to NPM for import support -- [ ] Add `consentChanged` event to trigger change in consent - - -# 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)); -``` - -# Deploy - -``` -yarn deploy -``` 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/s1.webpack.config.babel.js b/config/s1.webpack.config.babel.js index 6761cb19..e81150e9 100644 --- a/config/s1.webpack.config.babel.js +++ b/config/s1.webpack.config.babel.js @@ -35,12 +35,12 @@ module.exports = [ filename: '../reference.html', template: 's1/reference.hbs', inject: false, - inline: UglifyJS.minify(fs.readFileSync('./src/loader.js', 'utf8')).code, + inline: UglifyJS.minify(fs.readFileSync('./src/s1/loader.js', 'utf8')).code, version }), new CopyWebpackPlugin([ { - from: 'loader.js', + from: 's1/loader.js', to: './loader.js', transform(content) { // Just want to uglify and copy this file over diff --git a/package.json b/package.json index 1f9f77a7..304cb79d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "appnexus-cmp", - "version": "0.0.3", + "version": "0.0.1", "scripts": { "clean": "rm -rf dist", "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress --config config/webpack.config.babel.js", @@ -8,13 +8,13 @@ "start": "serve dist -s -c 1", "prestart": "npm run build", "build": "rimraf ./dist && yarn build:original && yarn build:s1", - "deploy": "yarn deploy:original && yarn deploy: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/christian-test --gzip --cache 1440 --invalidate '/christian-test/*.js /christian-test/*.html /christian-test/docs/**'", + "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/christian-test/$npm_package_version --gzip --preventUpdates --immutable --ext js", + "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", diff --git a/src/lib/vendor.js b/src/lib/vendor.js index ef0ab774..07ecd05e 100644 --- a/src/lib/vendor.js +++ b/src/lib/vendor.js @@ -3,8 +3,6 @@ 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 */ diff --git a/src/s1/cmp.js b/src/s1/cmp.js index cb4f43d2..3d653d96 100644 --- a/src/s1/cmp.js +++ b/src/s1/cmp.js @@ -6,7 +6,7 @@ import 'core-js/fn/array/find'; import 'core-js/fn/array/map'; import 'core-js/fn/object/keys'; -import cmp from '../loader'; +import cmp from './loader'; import {init} from '../lib/init'; import log from '../lib/log'; import {readCookie, writeCookie} from "../lib/cookie/cookie"; diff --git a/src/s1/constants.js b/src/s1/constants.js deleted file mode 100644 index 8868038e..00000000 --- a/src/s1/constants.js +++ /dev/null @@ -1,31 +0,0 @@ -export const GDPRCountries = [ - 'AT', - 'BE', - 'BG', - 'HR', - 'CY', - 'CZ', - 'DK', - 'EE', - 'FI', - 'FR', - 'DE', - 'GR', - 'HU', - 'IE', - 'IT', - 'LV', - 'LT', - 'LU', - 'MT', - 'NL', - 'PL', - 'PT', - 'RO', - 'SK', - 'SI', - 'ES', - 'SE', - 'GB', - 'US' // @FIXME @christian remove -]; diff --git a/src/s1/embed.js b/src/s1/embed.js deleted file mode 100644 index 0263451b..00000000 --- a/src/s1/embed.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * creates and manages global cmp and __cmp objects on window - * cmp queues incoming requests - * once loaded, cmp invokes cmp.processCommand() - */ -(function() { - const countries = [ - 'AT', - 'BE', - 'BG', - 'HR', - 'CY', - 'CZ', - 'DK', - 'EE', - 'FI', - 'FR', - 'DE', - 'GR', - 'HU', - 'IE', - 'IT', - 'LV', - 'LT', - 'LU', - 'MT', - 'NL', - 'PL', - 'PT', - 'RO', - 'SK', - 'SI', - 'ES', - 'SE', - 'GB', - 'US' // FIXME @charden remove - ]; - - let cmpLoader = (function(cmp, __cmp) { - return function(scriptSrc, countryCode) { - // 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 - }); - } - }; - - // 3. load cmp if script inlined - if (countries.indexOf(countryCode) >= 0 && scriptSrc) { - (scriptEl = document.createElement(script)), - (scriptParentEl = document.getElementsByTagName(script)[0]); - scriptEl.async = 1; - scriptEl.src = scriptSrc; - scriptParentEl.parentNode.insertBefore(scriptEl, scriptParentEl); - } - - // 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; - } else if (typeof define === 'function' && define.amd) { - define([], () => { - return cmpLoader; - }); - } else { - window.cmpLoader = cmpLoader; // External script defined here for ease of use - } -})(); diff --git a/src/loader.js b/src/s1/loader.js similarity index 100% rename from src/loader.js rename to src/s1/loader.js diff --git a/src/test/loader.import.test.js b/src/s1/test/loader.import.test.js similarity index 91% rename from src/test/loader.import.test.js rename to src/s1/test/loader.import.test.js index 155fb6b9..3e55235a 100644 --- a/src/test/loader.import.test.js +++ b/src/s1/test/loader.import.test.js @@ -1,7 +1,7 @@ /* eslint-disable max-nested-callbacks */ import { expect } from 'chai'; -import { init } from '../lib/init'; +import { init } from '../../lib/init'; const fakeScriptSrc = "./fake-loader-src.js"; let cmpLoader; @@ -44,7 +44,7 @@ describe('cmpLoader as import', () => { beforeEach(() => { appendChild = window.document.body.appendChild = jest.fn(() => { - require('../s1/cmp'); // need to require this here because there is no built version that we can script load + require('../cmp'); // need to require this here because there is no built version that we can script load }); }); diff --git a/src/test/loader.inline.test.js b/src/s1/test/loader.inline.test.js similarity index 94% rename from src/test/loader.inline.test.js rename to src/s1/test/loader.inline.test.js index 7385b4aa..abc5dbce 100644 --- a/src/test/loader.inline.test.js +++ b/src/s1/test/loader.inline.test.js @@ -3,7 +3,7 @@ import { expect } from 'chai'; import fs from 'fs'; -import vendorlist from '../docs/assets/vendorlist.json'; +import vendorlist from '../../docs/assets/vendorlist.json'; // import pubvendorsStub // import vendorlistStub const fakeScriptSrc = './fake-loader-src.js'; @@ -13,7 +13,7 @@ describe('cmpLoader as script tag', () => { beforeEach(() => { appendChild = window.document.body.appendChild = jest.fn(() => {}); - const content = fs.readFileSync('./src/loader.js'); + const content = fs.readFileSync('./src/s1/loader.js'); eval(content + '; global.cmp = cmp'); }); @@ -111,7 +111,7 @@ describe('cmpLoader as script tag', () => { return Promise.resolve(src); }); appendChild = window.document.body.appendChild = jest.fn(() => { - require('../s1/cmp'); // need to require this here because there is no built version that we can script load + require('../cmp'); // need to require this here because there is no built version that we can script load }); }); From 783ed1356d058a6f194837a5d741beb2a4899ef4 Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Tue, 3 Jul 2018 13:19:19 -0700 Subject: [PATCH 10/11] (feat) added .node_version --- .node_version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .node_version 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 From e42792b2f42c8c52886225b7542f223e509791ba Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Tue, 3 Jul 2018 15:11:20 -0700 Subject: [PATCH 11/11] (refactor) remove codesmell from package json and babel config --- config/common.webpack.config.babel.js | 2 -- package.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/config/common.webpack.config.babel.js b/config/common.webpack.config.babel.js index 0ab05955..b3b23e9d 100644 --- a/config/common.webpack.config.babel.js +++ b/config/common.webpack.config.babel.js @@ -2,10 +2,8 @@ /* eslint-disable import/no-extraneous-dependencies */ import webpack from 'webpack'; -// import CopyWebpackPlugin from 'copy-webpack-plugin'; import autoprefixer from 'autoprefixer'; import path from 'path'; -// import UglifyJS from 'uglify-es'; import packageJson from '../package.json'; const ENV = process.env.NODE_ENV || 'development'; diff --git a/package.json b/package.json index 304cb79d..34bcb918 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "appnexus-cmp", "version": "0.0.1", "scripts": { - "clean": "rm -rf dist", + "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",