From bc445c336c9de380968db449a69ab818cb757223 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benedikt=20R=C3=B6tsch?=
Date: Sun, 4 Apr 2021 20:53:39 +0200
Subject: [PATCH 1/6] feat(eslint): introduce new consent-manager eslint-plugin
---
.eslintrc.js | 45 ++++++++++---------
package.json | 1 +
.../eslint-plugin-consent-manager/index.js | 33 ++++++++++++++
.../package.json | 40 +++++++++++++++++
yarn.lock | 3 ++
5 files changed, 102 insertions(+), 20 deletions(-)
create mode 100644 packages/eslint-plugin-consent-manager/index.js
create mode 100644 packages/eslint-plugin-consent-manager/package.json
diff --git a/.eslintrc.js b/.eslintrc.js
index 209de498..592424ad 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,25 +1,30 @@
module.exports = {
- "extends": [
- "react-app",
- "prettier/@typescript-eslint",
- "plugin:prettier/recommended"
+ extends: [
+ 'react-app',
+ 'prettier/@typescript-eslint',
+ 'plugin:prettier/recommended',
],
- "settings": {
- "react": {
- "version": "detect"
- }
+ settings: {
+ react: {
+ version: 'detect',
+ },
},
- "rules": {
- "@typescript-eslint/semi": ["error", "never"],
- "@typescript-eslint/member-delimiter-style": ["error", {
- "multiline": {
- "delimiter": "none",
- "requireLast": true
+ plugins: ['eslint-plugin-consent-manager'],
+ rules: {
+ 'consent-manager/do-not-inject-scripts': ['error'],
+ '@typescript-eslint/semi': ['error', 'never'],
+ '@typescript-eslint/member-delimiter-style': [
+ 'error',
+ {
+ multiline: {
+ delimiter: 'none',
+ requireLast: true,
+ },
+ singleline: {
+ delimiter: 'comma',
+ requireLast: false,
+ },
},
- "singleline": {
- "delimiter": "comma",
- "requireLast": false
- }
- }]
- }
+ ],
+ },
}
diff --git a/package.json b/package.json
index bb352df3..fc7caa88 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8",
"cypress": "^5.2.0",
+ "eslint-plugin-consent-manager": "./packages/eslint-plugin-consent-manager",
"lerna": "^3.15.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
diff --git a/packages/eslint-plugin-consent-manager/index.js b/packages/eslint-plugin-consent-manager/index.js
new file mode 100644
index 00000000..84ab5c9a
--- /dev/null
+++ b/packages/eslint-plugin-consent-manager/index.js
@@ -0,0 +1,33 @@
+module.exports = {
+ rules: {
+ 'do-not-inject-scripts': {
+ meta: {
+ docs: {
+ description:
+ 'Return the script tags instead of injecting them yourself. That way they can be removed when the user revokes consent.',
+ },
+ schema: [],
+ },
+ create: function(context) {
+ return {
+ CallExpression: function(node) {
+ var callee = node.callee
+
+ if (
+ callee?.object?.name === 'document' &&
+ callee?.property?.name === 'createElement' &&
+ node?.arguments[0] &&
+ node?.arguments[0].value === 'script'
+ ) {
+ context.report({
+ node,
+ message:
+ 'Return the script tags instead of injecting them yourself. That way they can be removed when the user revokes consent.',
+ })
+ }
+ },
+ }
+ },
+ },
+ },
+}
diff --git a/packages/eslint-plugin-consent-manager/package.json b/packages/eslint-plugin-consent-manager/package.json
new file mode 100644
index 00000000..0b17fad6
--- /dev/null
+++ b/packages/eslint-plugin-consent-manager/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "eslint-plugin-consent-manager",
+ "version": "0.0.0",
+ "description": "eslint plugin for consent-manager development",
+ "main": "index.js",
+ "author": {
+ "name": "techboi GmbH",
+ "email": "opensource@techboi.io",
+ "url": "https://techboi.io/"
+ },
+ "contributors": [
+ {
+ "name": "Benedikt Rötsch",
+ "email": "benedikt@techboi.io",
+ "url": "https://twitter.com/axe312ger"
+ },
+ {
+ "name": "Stephan Schneider",
+ "email": "stephanschndr@gmail.com",
+ "url": "https://twitter.com/zcei_ger"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/techboi/consent-manager",
+ "directory": "packages/eslint-plugin-consent-manager"
+ },
+ "homepage": "https://techboi.github.io/consent-manager/",
+ "keywords": [
+ "gdpr",
+ "ccpa",
+ "consent",
+ "consent-management",
+ "eslint"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": "^7.0.0"
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index b6b92aef..c5a138bc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7032,6 +7032,9 @@ eslint-module-utils@^2.6.0:
debug "^2.6.9"
pkg-dir "^2.0.0"
+eslint-plugin-consent-manager@./packages/eslint-plugin-consent-manager:
+ version "0.0.0"
+
eslint-plugin-flowtype@^3.13.0:
version "3.13.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-3.13.0.tgz#e241ebd39c0ce519345a3f074ec1ebde4cf80f2c"
From e2f80ebfb8882853bc8c2162a18d710f268b50a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benedikt=20R=C3=B6tsch?=
Date: Mon, 5 Apr 2021 20:23:36 +0200
Subject: [PATCH 2/6] refactor: WrapperComponent becomes ScriptInjector
---
cypress/integration/basic.spec.ts | 55 +------
cypress/integration/privacy-shield.spec.ts | 34 ++++
cypress/integration/script-injectors.spec.ts | 56 +++++++
cypress/integration/tracking-events.spec.ts | 80 +++++++++
.../integration/tracking-pageviews.spec.ts | 80 +++++++++
cypress/integration/tracking.spec.ts | 155 ------------------
cypress/support/commands.ts | 2 +-
example/index.html | 2 +-
example/index.tsx | 18 +-
example/integrations/social-video-inc.tsx | 2 +-
.../integrations/tracker-innocent-pixel.tsx | 42 +++++
example/integrations/tracker-red-box-ltd.tsx | 95 +++++------
example/package.json | 1 +
example/routes/home.tsx | 13 +-
example/static/red-box-ltd.js | 34 ++++
example/static/zero-pixel.png | Bin 0 -> 68 bytes
...nts.tsx => IntegrationScriptInjectors.tsx} | 18 +-
packages/core/src/config.ts | 10 +-
packages/core/src/index.tsx | 12 +-
packages/core/src/integration-helpers.tsx | 115 ++++++++++++-
packages/docs/docs/getting-started.md | 2 +-
.../src/components/integration-profile.tsx | 6 +-
.../integration-google-analytics/src/index.ts | 4 +-
.../src/index.ts | 4 +-
packages/integration-hubspot/src/index.ts | 4 +-
packages/integration-linkedin/src/index.ts | 4 +-
packages/integration-matomo/src/index.tsx | 4 +-
packages/integration-segment/src/index.ts | 4 +-
28 files changed, 538 insertions(+), 318 deletions(-)
create mode 100644 cypress/integration/privacy-shield.spec.ts
create mode 100644 cypress/integration/script-injectors.spec.ts
create mode 100644 cypress/integration/tracking-events.spec.ts
create mode 100644 cypress/integration/tracking-pageviews.spec.ts
delete mode 100644 cypress/integration/tracking.spec.ts
create mode 100644 example/integrations/tracker-innocent-pixel.tsx
create mode 100644 example/static/red-box-ltd.js
create mode 100644 example/static/zero-pixel.png
rename packages/core/src/components/{IntegrationWrapperComponents.tsx => IntegrationScriptInjectors.tsx} (66%)
diff --git a/cypress/integration/basic.spec.ts b/cypress/integration/basic.spec.ts
index eafb0560..6dff360f 100644
--- a/cypress/integration/basic.spec.ts
+++ b/cypress/integration/basic.spec.ts
@@ -1,6 +1,6 @@
///
-describe('Wrapping Component', () => {
+describe('Basics', () => {
before(() => {
cy.visit('http://localhost:1234/')
})
@@ -13,57 +13,4 @@ describe('Wrapping Component', () => {
container.should('contain', 'Video Inc.')
container.should('contain', 'Red Box Ltd.')
})
-
- it('does not render wrapping component by default', () => {
- cy.get('[data-testid="consent-manager-wrapping-component"]').should(
- 'not.exist'
- )
- })
-
- it('renders wrapping component after making decision', () => {
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.get('[data-testid="consent-manager-wrapping-component"]').should('exist')
- })
-
- it('removes wrapping component after revoking decision', () => {
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.get('[data-testid="consent-manager-wrapping-component"]').should(
- 'not.exist'
- )
- })
-})
-
-describe('Privacy Shield', () => {
- before(() => {
- cy.visit('http://localhost:1234/video')
- })
- it('renders privacy shield', () => {
- cy.get('[data-testid="consent-manager-privacy-shield"]').contains(
- 'Video Inc. is a popular service to share clips of cats.'
- )
- })
-
- it('renders form component', () => {
- const container = cy.get('[data-testid="consent-manager-form-container"]')
- container.should('contain', 'Video Inc.')
- container.should('contain', 'Red Box Ltd.')
- })
-
- it('renders video component after making decision', () => {
- cy.toggleIntegration('Video Inc.')
-
- cy.get('[data-testid="consent-manager-video-component"]').contains(
- 'Video component with id rick-roll'
- )
- })
-
- it('renders privacy shield after revoking decision', () => {
- cy.toggleIntegration('Video Inc.')
-
- cy.get('[data-testid="consent-manager-privacy-shield"]').contains(
- 'Video Inc. is a popular service to share clips of cats.'
- )
- })
})
diff --git a/cypress/integration/privacy-shield.spec.ts b/cypress/integration/privacy-shield.spec.ts
new file mode 100644
index 00000000..368293f5
--- /dev/null
+++ b/cypress/integration/privacy-shield.spec.ts
@@ -0,0 +1,34 @@
+///
+
+describe('Privacy Shield', () => {
+ before(() => {
+ cy.visit('http://localhost:1234/video')
+ })
+ it('renders privacy shield', () => {
+ cy.get('[data-testid="consent-manager-privacy-shield"]').contains(
+ 'Video Inc. is a popular service to share clips of cats.'
+ )
+ })
+
+ it('renders form component', () => {
+ const container = cy.get('[data-testid="consent-manager-form-container"]')
+ container.should('contain', 'Video Inc.')
+ container.should('contain', 'Red Box Ltd.')
+ })
+
+ it('renders video component after making decision', () => {
+ cy.toggleIntegration('Video Inc.')
+
+ cy.get('[data-testid="consent-manager-video-component"]').contains(
+ 'Video component with id rick-roll'
+ )
+ })
+
+ it('renders privacy shield after revoking decision', () => {
+ cy.toggleIntegration('Video Inc.')
+
+ cy.get('[data-testid="consent-manager-privacy-shield"]').contains(
+ 'Video Inc. is a popular service to share clips of cats.'
+ )
+ })
+})
diff --git a/cypress/integration/script-injectors.spec.ts b/cypress/integration/script-injectors.spec.ts
new file mode 100644
index 00000000..e8664f64
--- /dev/null
+++ b/cypress/integration/script-injectors.spec.ts
@@ -0,0 +1,56 @@
+///
+
+describe('Script Injector: script', () => {
+ const selector = 'script#red-box-ltd'
+
+ before(() => {
+ cy.visit('http://localhost:1234/')
+ })
+ it('does not inject script by default', () => {
+ cy.get(selector).should('not.exist')
+ })
+
+ it('injects script after making decision', () => {
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get(selector).should('have.attr', 'async')
+ cy.get(selector).should('have.attr', 'defer')
+ cy.get(selector)
+ .should('have.attr', 'type')
+ .should('equals', 'text/javascript')
+
+ cy.window()
+ .its('rbltd.push')
+ .should('exist')
+ })
+
+ it('removes script revoking decision', () => {
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get(selector).should('not.exist')
+ })
+})
+
+describe('Script Injector: img tag', () => {
+ const selector = 'img[src="/zero-pixel.png"]'
+
+ before(() => {
+ cy.visit('http://localhost:1234/')
+ })
+ it('does not inject img by default', () => {
+ cy.get(selector).should('not.exist')
+ })
+
+ it('injects img after making decision', () => {
+ cy.toggleIntegration('Innocent Pixel')
+
+ cy.get(selector).should('have.attr', 'height')
+ cy.get(selector).should('have.attr', 'width')
+ })
+
+ it('removes img revoking decision', () => {
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get(selector).should('not.exist')
+ })
+})
diff --git a/cypress/integration/tracking-events.spec.ts b/cypress/integration/tracking-events.spec.ts
new file mode 100644
index 00000000..a9330fd8
--- /dev/null
+++ b/cypress/integration/tracking-events.spec.ts
@@ -0,0 +1,80 @@
+describe('Pave View Tracking', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:1234/', {
+ onBeforeLoad(win) {
+ cy.spy(win.console, 'log').as('console.log')
+ },
+ })
+ })
+
+ it('does not initialize tracking by default', () => {
+ cy.get('@console.log')
+ .should('not.be.calledWith', 'Initializing Red Box Ltd. tracking')
+ .should('not.be.calledWith', 'page view: /')
+ .should('not.be.called')
+ cy.window()
+ .its('rbltd')
+ .should('not.exist')
+ cy.get('[data-testid="consent-manager-wrapping-component"]').should(
+ 'not.exist'
+ )
+ })
+
+ it('initializes tracking on user consent and logs initial page view', () => {
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.window()
+ .its('rbltd.push')
+ .should('exist')
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /')
+ .should('not.be.calledWith', 'page view: /video')
+ .should('be.calledOnce')
+ })
+
+ it('tracks page views when route changes to video and back', () => {
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /')
+ .should('be.calledOnce')
+
+ cy.get('[data-testid=example-nav-video]').click()
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /video')
+ .should('be.calledTwice')
+
+ cy.get('[data-testid=example-nav-home]').click()
+
+ cy.get('@console.log').then(logSpy => {
+ expect(logSpy.args[0][0]).to.equal(logSpy.args[2][0])
+ })
+ cy.get('@console.log').should('be.calledThrice')
+ })
+
+ it('disables tracking when user revokes consent', () => {
+ cy.get('[data-testid="consent-manager-wrapping-component"]').should(
+ 'not.exist'
+ )
+
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get('@console.log').should('be.calledOnce')
+
+ cy.get('[data-testid=example-nav-video]').click()
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /video')
+ .should('be.calledTwice')
+
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get('@console.log').should('be.calledTwice')
+
+ cy.get('[data-testid=example-nav-home]').click()
+
+ cy.get('@console.log').should('be.calledTwice')
+ })
+})
diff --git a/cypress/integration/tracking-pageviews.spec.ts b/cypress/integration/tracking-pageviews.spec.ts
new file mode 100644
index 00000000..a9330fd8
--- /dev/null
+++ b/cypress/integration/tracking-pageviews.spec.ts
@@ -0,0 +1,80 @@
+describe('Pave View Tracking', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:1234/', {
+ onBeforeLoad(win) {
+ cy.spy(win.console, 'log').as('console.log')
+ },
+ })
+ })
+
+ it('does not initialize tracking by default', () => {
+ cy.get('@console.log')
+ .should('not.be.calledWith', 'Initializing Red Box Ltd. tracking')
+ .should('not.be.calledWith', 'page view: /')
+ .should('not.be.called')
+ cy.window()
+ .its('rbltd')
+ .should('not.exist')
+ cy.get('[data-testid="consent-manager-wrapping-component"]').should(
+ 'not.exist'
+ )
+ })
+
+ it('initializes tracking on user consent and logs initial page view', () => {
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.window()
+ .its('rbltd.push')
+ .should('exist')
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /')
+ .should('not.be.calledWith', 'page view: /video')
+ .should('be.calledOnce')
+ })
+
+ it('tracks page views when route changes to video and back', () => {
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /')
+ .should('be.calledOnce')
+
+ cy.get('[data-testid=example-nav-video]').click()
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /video')
+ .should('be.calledTwice')
+
+ cy.get('[data-testid=example-nav-home]').click()
+
+ cy.get('@console.log').then(logSpy => {
+ expect(logSpy.args[0][0]).to.equal(logSpy.args[2][0])
+ })
+ cy.get('@console.log').should('be.calledThrice')
+ })
+
+ it('disables tracking when user revokes consent', () => {
+ cy.get('[data-testid="consent-manager-wrapping-component"]').should(
+ 'not.exist'
+ )
+
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get('@console.log').should('be.calledOnce')
+
+ cy.get('[data-testid=example-nav-video]').click()
+
+ cy.get('@console.log')
+ .should('be.calledWith', 'page view: /video')
+ .should('be.calledTwice')
+
+ cy.toggleIntegration('Red Box Ltd.')
+
+ cy.get('@console.log').should('be.calledTwice')
+
+ cy.get('[data-testid=example-nav-home]').click()
+
+ cy.get('@console.log').should('be.calledTwice')
+ })
+})
diff --git a/cypress/integration/tracking.spec.ts b/cypress/integration/tracking.spec.ts
deleted file mode 100644
index 7cb5ab20..00000000
--- a/cypress/integration/tracking.spec.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-describe('pave view tracking', () => {
- beforeEach(() => {
- cy.visit('http://localhost:1234/', {
- onBeforeLoad(win) {
- cy.spy(win.console, 'log').as('console.log')
- },
- })
- })
-
- it('does not initialize tracking by default', () => {
- cy.get('@console.log')
- .should('not.be.calledWith', 'Initializing Red Box Ltd. tracking')
- .should('not.be.calledWith', 'page view: /')
- .should('not.be.called')
- cy.window()
- .its('rbltd')
- .should('not.exist')
- cy.get('[data-testid="consent-manager-wrapping-component"]').should(
- 'not.exist'
- )
- })
-
- it('initializes tracking on user consent and logs initial page view', () => {
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.window()
- .its('rbltd.trackEvent')
- .should('exist')
- cy.window()
- .its('rbltd.trackPageView')
- .should('exist')
-
- cy.get('[data-testid="consent-manager-wrapping-component"]').should('exist')
-
- cy.get('@console.log')
- .should('be.calledWith', 'Initializing Red Box Ltd. tracking')
- .should('be.calledWith', 'page view: /')
- .should('not.be.calledWith', 'page view: /video')
- .should('be.calledTwice')
- })
-
- it('tracks page views when route changes to video and back', () => {
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.get('@console.log')
- .should('be.calledWith', 'Initializing Red Box Ltd. tracking')
- .should('be.calledWith', 'page view: /')
- .should('be.calledTwice')
-
- cy.get('[data-testid=example-nav-video]').click()
-
- cy.get('@console.log')
- .should('be.calledWith', 'page view: /video')
- .should('be.calledThrice')
-
- cy.get('[data-testid=example-nav-home]').click()
-
- cy.get('@console.log').then(logSpy => {
- expect(logSpy.args[1][0]).to.equal(logSpy.args[3][0])
- expect(logSpy).to.have.callCount(4)
- })
- })
-
- it('disables tracking when user revokes consent', () => {
- cy.get('[data-testid="consent-manager-wrapping-component"]').should(
- 'not.exist'
- )
-
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.get('[data-testid="consent-manager-wrapping-component"]').should('exist')
-
- cy.get('@console.log').should('be.calledTwice')
-
- cy.get('[data-testid=example-nav-video]').click()
-
- cy.get('[data-testid="consent-manager-wrapping-component"]').should('exist')
- cy.get('@console.log')
- .should('be.calledWith', 'page view: /video')
- .should('be.calledThrice')
-
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.get('@console.log').should('be.calledThrice')
- cy.get('[data-testid="consent-manager-wrapping-component"]').should(
- 'not.exist'
- )
-
- cy.get('[data-testid=example-nav-home]').click()
-
- cy.get('@console.log').should('be.calledThrice')
- cy.get('[data-testid="consent-manager-wrapping-component"]').should(
- 'not.exist'
- )
- })
-})
-
-describe('custom event tracking', () => {
- beforeEach(() => {
- cy.visit('http://localhost:1234/', {
- onBeforeLoad(win) {
- cy.spy(win.console, 'log').as('console.log')
- cy.spy(win, 'alert').as('alert')
- },
- })
- })
-
- it('enables event tracking on on user consent', () => {
- cy.get('[data-testid=example-button]').should(
- 'contain',
- 'Do not click here - nothing will happen'
- )
-
- // wait one tick to mount wrapper components
- // @todo consider if we really need to wrap the whole thing on client again!
- cy.wait(0)
-
- cy.get('@console.log').should('not.be.called')
-
- cy.get('[data-testid=example-button]').click()
-
- cy.get('@console.log').should('not.be.called')
- cy.get('@alert').should('not.be.called')
-
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.get('[data-testid=example-button]').should(
- 'contain',
- 'Do not click here - we will track you'
- )
-
- cy.get('@console.log').should('be.calledTwice')
-
- cy.get('[data-testid=example-button]').click()
-
- cy.get('@console.log')
- .should('be.calledThrice')
- .should('be.calledWith', 'custom event tracked')
-
- cy.get('@alert')
- .should('be.calledOnce')
- .should('be.calledWith', 'told ya! click watching you closely')
-
- cy.toggleIntegration('Red Box Ltd.')
-
- cy.get('[data-testid=example-button]').click()
-
- cy.get('@alert').should('be.calledOnce')
- cy.get('@console.log').should('be.calledThrice')
- cy.get('[data-testid=example-button]').should(
- 'contain',
- 'Do not click here - nothing will happen'
- )
- })
-})
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index 5fc76392..c2c5163f 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -25,7 +25,7 @@
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add('toggleIntegration', integrationLabel => {
- // wait one tick to mount wrapper components
+ // wait one tick to execute script injectors
// @todo consider if we really need to wrap the whole thing on client again!
cy.wait(0)
diff --git a/example/index.html b/example/index.html
index 547e2e04..42cc92c1 100644
--- a/example/index.html
+++ b/example/index.html
@@ -4,7 +4,7 @@
- Playground
+ Consent Manager Example
diff --git a/example/index.tsx b/example/index.tsx
index 076f08fe..f4db6587 100644
--- a/example/index.tsx
+++ b/example/index.tsx
@@ -17,26 +17,30 @@ import {
import RouteHome from './routes/home'
import RouteVideo from './routes/video'
-import { createVideoIncIntegration } from './integrations/social-video-inc'
+import { videoIncIntegration } from './integrations/social-video-inc'
+import { innocentPixelIntegration } from './integrations/tracker-innocent-pixel'
import {
- createRedBoxLtdIntegration,
+ redBoxLtdIntegration,
useRedBoxLtd,
} from './integrations/tracker-red-box-ltd'
const consentManagerConfig: ConsentManagerConfig = {
- integrations: [createVideoIncIntegration(), createRedBoxLtdIntegration()],
+ integrations: [
+ videoIncIntegration(),
+ redBoxLtdIntegration(),
+ innocentPixelIntegration(),
+ ],
}
const PageViewTracker: React.FC = () => {
const location = useLocation()
- const { trackPageView } = useRedBoxLtd()
+ const redBoxLtdTracker = useRedBoxLtd()
React.useEffect(() => {
- // @todo find proper solution to ensure page view is tracked after tracker api is initialized
window.setTimeout(() => {
- trackPageView(location)
+ redBoxLtdTracker && redBoxLtdTracker.push(['page', location.pathname])
}, 0)
- }, [location, trackPageView])
+ }, [location, redBoxLtdTracker])
return null
}
diff --git a/example/integrations/social-video-inc.tsx b/example/integrations/social-video-inc.tsx
index 8e3b77cc..ca47f64f 100644
--- a/example/integrations/social-video-inc.tsx
+++ b/example/integrations/social-video-inc.tsx
@@ -19,7 +19,7 @@ const Icon: React.FC = () => (
)
-export function createVideoIncIntegration(): IntegrationConfig {
+export function videoIncIntegration(): IntegrationConfig {
return {
id: 'video-platform',
title: 'Video Inc.',
diff --git a/example/integrations/tracker-innocent-pixel.tsx b/example/integrations/tracker-innocent-pixel.tsx
new file mode 100644
index 00000000..54b2cc5f
--- /dev/null
+++ b/example/integrations/tracker-innocent-pixel.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react'
+
+import { IntegrationConfig } from '@consent-manager/core'
+
+const Icon: React.FC = () => (
+
+)
+
+const ScriptInjector: React.FC = () => {
+ return (
+
+ )
+}
+
+export function innocentPixelIntegration(): IntegrationConfig {
+ return {
+ id: 'innocent-pixel',
+ title: 'Innocent Pixel',
+ category: 'statistics',
+ description: 'Example integration that injects a pixel tracking technique.',
+ color: '#2d4876',
+ contrastColor: '#fff',
+ Icon,
+ ScriptInjector,
+ }
+}
diff --git a/example/integrations/tracker-red-box-ltd.tsx b/example/integrations/tracker-red-box-ltd.tsx
index a82b8f8a..590f58c1 100644
--- a/example/integrations/tracker-red-box-ltd.tsx
+++ b/example/integrations/tracker-red-box-ltd.tsx
@@ -1,31 +1,22 @@
import * as React from 'react'
-import { IntegrationConfig, useDecision, Tracker } from '@consent-manager/core'
+import {
+ IntegrationConfig,
+ useDecision,
+ Tracker,
+ useScript,
+ locateTracker,
+} from '@consent-manager/core'
+import { useEffect } from 'react'
declare global {
interface Window {
- rbltd?: RedBoxLtdWindow
- }
-}
-
-interface RedBoxLtdWindow extends Tracker {}
-
-const createRedBoxTracker = () => {
- console.log('Initializing Red Box Ltd. tracking')
- window.rbltd = {
- trackEvent: (...data) => {
- console.log('custom event tracked', data)
- alert(['told ya!', ...data].join(' '))
- },
- trackPageView: (location: Location) => {
- console.log(`page view: ${location.pathname}`, location)
- },
+ rbltd?: RedBoxLtdTracker
}
}
const Icon: React.FC = () => (
)
-// ensure that the tracker script will be initialized once in runtime
-let wasInitialized = false
+const ScriptInjector: React.FC = () => {
+ useEffect(() => {
+ window.rbltd = window.rbltd || []
-const WrapperComponent: React.FC = ({ children }) => {
- React.useEffect(() => {
- if (!wasInitialized) {
- createRedBoxTracker()
- wasInitialized = true
+ return () => {
+ delete window.rbltd
}
- }, [])
+ })
+
+ useScript('/red-box-ltd.js', { id: 'red-box-ltd' })
- return (
-
- {children}
-
- )
+ return null
}
-export function useRedBoxLtd(): Tracker {
+interface RedBoxLtdTracker extends Array>, Tracker {}
+
+// For usage non-react context when tracking page views (Docusaurus, Gatsby, ...)
+// @todo we need to save-guard this by checking if integration is actually enabled
+export function getRedBoxLtd() {
+ return window.rbltd
+}
+
+export function useRedBoxLtd(): RedBoxLtdTracker | null {
const [isEnabled] = useDecision('red-box-ltd')
+ const [tracker, setTracker] = React.useState(null)
- const redBoxLtdInterface = React.useMemo(() => {
- if (!isEnabled) {
- return {
- trackEvent: () => {},
- trackPageView: () => {},
- }
+ React.useEffect(() => {
+ if (isEnabled && !tracker) {
+ locateTracker('rbltd', setTracker)
}
+ }, [isEnabled, setTracker, tracker])
- return {
- trackEvent: (...args) =>
- window.rbltd &&
- window.rbltd.trackEvent &&
- window.rbltd.trackEvent(...args),
- trackPageView: (...args) =>
- window.rbltd &&
- window.rbltd.trackPageView &&
- window.rbltd.trackPageView(...args),
- }
- }, [isEnabled])
+ if (!isEnabled) {
+ return null
+ }
- return redBoxLtdInterface
+ return tracker
}
-export function createRedBoxLtdIntegration(): IntegrationConfig {
+export function redBoxLtdIntegration(): IntegrationConfig {
return {
id: 'red-box-ltd',
title: 'Red Box Ltd.',
category: 'statistics',
description:
- 'Adds red borders around your content, demonstrates use of components that do e.g. click tracking',
+ 'Example integration that injects scripts to demonstrate click and page tracking',
color: '#C21515',
contrastColor: '#fff',
Icon,
- WrapperComponent,
+ ScriptInjector,
}
}
diff --git a/example/package.json b/example/package.json
index e7e6e06a..d65823d9 100644
--- a/example/package.json
+++ b/example/package.json
@@ -17,6 +17,7 @@
"@types/react": "^16.9.11",
"@types/react-dom": "^16.8.4",
"parcel": "1.12.3",
+ "parcel-plugin-static-files-copy": "^2.6.0",
"typescript": "^3.4.5"
},
"alias": {
diff --git a/example/routes/home.tsx b/example/routes/home.tsx
index 42415c02..de6fdddf 100644
--- a/example/routes/home.tsx
+++ b/example/routes/home.tsx
@@ -3,7 +3,7 @@ import { useRedBoxLtd } from '../integrations/tracker-red-box-ltd'
import { useDecision } from '@consent-manager/core'
export default function RouteHome() {
- const { trackEvent } = useRedBoxLtd()
+ const redBoxLtdTracker = useRedBoxLtd()
const [isEnabled] = useDecision('red-box-ltd')
return (
@@ -18,7 +18,16 @@ export default function RouteHome() {
action.