Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ad attribution test flow #93

Merged
merged 4 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ We have couple of test domains, that all resolve to `privacy-test-pages.glitch.m
- `good.third-party.site` - non-tracking third party, it's not on our blocklist and will not be blocked by our clients
- `broken.third-party.site` - tracking third party that we can't block (e.g. due to brekage), it's on our blocklist, but it will not be blocked by our clients
- `bad.third-party.site` - tracking third party that's on our blocklist and our clients will block
- `search-company.example` - Simulated search provider
- `ad-company.example` - Simulated ad provider
- `payment-company.example` - Simulated payment provider
- `publisher-company.example`- Simulated publisher website

### How to test it locally

Expand All @@ -42,11 +46,16 @@ Many of the test pages can be visited via `http://localhost`, but browsers somet

If you're using Firefox, you can use a pref to force hostnames to resolve to `127.0.0.1`:
1. Go to `about:config`
2. Set `network.dns.localDomains` to `first-party.example,hsts.first-party.example,third-party.example`.
2. Set `network.dns.localDomains` to `first-party.example,hsts.first-party.example,third-party.example,search-company.example,www.ad-company.example,convert.ad-company.example,publiher-company.example,payment-company.example`.

If you're testing in a browser other than Firefox, you'll have to edit your OS's hosts file to add the following lines:
```
# Privacy Test Pages (https://github.com/duckduckgo/privacy-test-pages)
127.0.0.1 www.search-company.example
127.0.0.1 www.ad-company.example
127.0.0.1 convert.ad-company.example
127.0.0.1 www.publisher-company.example
127.0.0.1 www.payment-company.example
127.0.0.1 first-party.example
127.0.0.1 hsts.first-party.example
127.0.0.1 third-party.example
Expand All @@ -68,10 +77,10 @@ mkcert -install

Then, in the root directory of `privacy-test-pages`, run:
```
mkcert first-party.example "*.first-party.example" third-party.example "*.third-party.example"
mkcert first-party.example "*.first-party.example" third-party.example "*.third-party.example" search-company.example "*.search-company.example" ad-company.example "*.ad-company.example" publisher-company.example "*.publisher-company.example" payment-company.example "*.payment-company.example"
```

This will generate two files (`first-party.example+3-key.pem` and `first-party.example+3.pem`) in the root directory. Express will automatically pick these up when you start the server (`node server.js`).
This will generate two files (`first-party.example+11-key.pem` and `first-party.example+11.pem`) in the root directory. Express will automatically pick these up when you start the server (`node server.js`).

## How to deploy it?

Expand Down
24 changes: 24 additions & 0 deletions adClickFlow/ad/convert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
console.log('Ad conversion');

const pixelUrl = new URL('./ping.gif', document.currentScript.src);

function fireResource (status) {
window.dispatchEvent(new CustomEvent('resourceLoad', {
detail: {
url: pixelUrl.href,
status: status
}
}));
}

const img = document.createElement('img');
img.src = pixelUrl;
img.style.display = 'none';
img.onload = () => {
console.log('Ad conversion complete');
fireResource('loaded');
};
img.onerror = () => {
fireResource('blocked');
};
document.body.appendChild(img);
Binary file added adClickFlow/ad/ping.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions adClickFlow/ad/track.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
console.log('Tracking');

const trackingImgUrl = new URL('./ping.gif', document.currentScript.src);
function fireResource (status) {
window.dispatchEvent(new CustomEvent('resourceLoad', {
detail: {
url: trackingImgUrl.href,
status
}
}));
}

const trackingImg = document.createElement('img');
trackingImg.src = trackingImgUrl;
trackingImg.style.display = 'none';
trackingImg.onload = () => {
fireResource('loaded');
};
trackingImg.onerror = () => {
fireResource('blocked');
};
document.body.appendChild(trackingImg);
27 changes: 27 additions & 0 deletions adClickFlow/pay/pay.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Publisher site - Home</title>
</head>
<body>

<h1>Pay First Party</h1>

<form action="process.html" method="post">
<label for="cc-name">Cardholder full Name</label><input name="cc-name" autocomplete="cc-name" id="cc-name" /><br />
<label for="cc-exp-month">Card number</label><input name="cc-number" autocomplete="cc-number" id="cc-number" /><br />
<label for="cc-exp-month">Month</label><input name="cc-exp-month" autocomplete="cc-exp-month" id="cc-exp-month" /><br />
<label for="cc-exp-year">Year</label><input name="cc-exp-year" autocomplete="cc-exp-year" id="cc-exp-year" /><br />
<label for="cc-csc">CSC</label><input name="cc-csc" autocomplete="cc-csc" id="cc-csc" /><br />
<button id="pay-button">Pay</button>
</form>

<script type="module">
import { initializeBoilerplate } from './shared/utils.mjs'
initializeBoilerplate()
</script>

</body>
</html>
37 changes: 37 additions & 0 deletions adClickFlow/pub/checkout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Checkout page</title>
</head>
<body>

<h1><a href="./" id="home-page">Publisher site</a></h1>
<h2>Checkout</h2>

<p>Click the link to complete the purchase</p>

<form action="process.html" method="post">
<label for="cc-name">Cardholder full Name</label><input name="cc-name" autocomplete="cc-name" id="cc-name" /><br />
<label for="cc-exp-month">Card number</label><input name="cc-number" autocomplete="cc-number" id="cc-number" /><br />
<label for="cc-exp-month">Month</label><input name="cc-exp-month" autocomplete="cc-exp-month" id="cc-exp-month" /><br />
<label for="cc-exp-year">Year</label><input name="cc-exp-year" autocomplete="cc-exp-year" id="cc-exp-year" /><br />
<label for="cc-csc">CSC</label><input name="cc-csc" autocomplete="cc-csc" id="cc-csc" /><br />
<button id="pay-button">Pay</button>
</form>

<button id="payment-company">Pay with payment-company!</button>

<script type="module">
import { initializeBoilerplate, getPaymentGatewayUrl } from './shared/utils.mjs'
initializeBoilerplate();

const button = document.getElementById('payment-company');
button.addEventListener('click', () => {
window.location = getPaymentGatewayUrl(globalThis.location.hostname);
});
</script>

</body>
</html>
20 changes: 20 additions & 0 deletions adClickFlow/pub/convert.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Checkout complete page</title>
</head>
<body>

<h1><a href="./">Publisher site</a></h1>

<p>Order complete; Thanks for the purchase!</p>

<script type="module">
import { initializeBoilerplate } from './shared/utils.mjs'
initializeBoilerplate();
</script>

</body>
</html>
47 changes: 47 additions & 0 deletions adClickFlow/pub/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Publisher site - Home</title>
</head>
<body>

<h1><a href="./" id="home-page">Publisher site</a></h1>

<template id="mylisting">
<section class="prod">
<h3><a href="#" target="_blank"></a></h3>
<div class="price"></div>
</section>
</template>

<main></main>

<script type="module">
import { products, initializeBoilerplate } from './shared/utils.mjs'
initializeBoilerplate();

const mainElement = document.querySelector('main');
const listingTemplate = document.getElementById('mylisting');
let productListingId = 1;
for (const productId in products) {
const product = products[productId];
renderListing(productId, product, productListingId);
productListingId++
}

function renderListing(productId, product, id) {
const listingElement = listingTemplate.content.cloneNode(true);
const link = listingElement.querySelector('h3 a');
link.href = `./product.html?p=${productId}`;
link.textContent = product.name;
link.id = `product-link-${id}`;
const price = listingElement.querySelector('.price');
price.textContent = '$' + product.price.join('.');
mainElement.appendChild(listingElement);
}
</script>

</body>
</html>
23 changes: 23 additions & 0 deletions adClickFlow/pub/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { products } from './shared/utils.mjs'
function scriptLoadedCallback(event) {
console.log('Script loaded:', event.target.src, event.detail.src);
}
window.addEventListener('scriptloaded', scriptLoadedCallback);


const productId = new URL(location.href).searchParams.get('p') || 12;
const mainElement = document.querySelector('main');
const productTemplate = document.getElementById('myproduct');
renderProduct(products[productId] || products[12]);

function renderProduct(product) {
const productElement = productTemplate.content.cloneNode(true);
const link = productElement.querySelector('h2 a');
link.textContent = product.name
document.title = 'Publisher ' + product.name
const summary = productElement.querySelector('summary');
summary.textContent = product.summary;
const price = productElement.querySelector('.price');
price.textContent = '$' + product.price.join('.');
mainElement.appendChild(productElement);
}
31 changes: 31 additions & 0 deletions adClickFlow/pub/product.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>product page</title>
</head>
<body>

<h1><a href="./" id="home-page">Publisher site</a></h1>

<template id="myproduct">
<section class="prod">
<h2><a href="/checkout.html" id="buy-now-heading"></a></h2>
<summary>
</summary>
<div class="price"></div>
<a href="/checkout.html" id="buy-now">Buy now</a>
</section>
</template>

<main></main>

<script type="module" src="index.mjs"></script>
<script type="module">
import { initializeBoilerplate } from './shared/utils.mjs'
initializeBoilerplate()
</script>

</body>
</html>
36 changes: 36 additions & 0 deletions adClickFlow/serp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Ad Click Flow</title>
</head>
<body>

<h1>Search engine</h1>

<template id="myad">
<section class="ad">
<h2><a href="#"></a></h2>
<summary>

</summary>
</section>
</template>


<main>
</main>

<style>
.ad {
border: 1px solid brown;
padding: 1em;
margin: 2em;
}
</style>

<script type="module" src="index.mjs"></script>

</body>
</html>
25 changes: 25 additions & 0 deletions adClickFlow/serp/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getAds } from '../shared/utils.mjs';

const mainElement = document.querySelector('main');
const myadTemplate = document.getElementById('myad');
const ads = getAds(globalThis.location.hostname);

for (const adId in ads) {
const ad = ads[adId];
const adElement = myadTemplate.content.cloneNode(true);
const link = adElement.querySelector('h2 a');
link.href = ad.url;
link.id = `ad-id-${adId}`;
if (ad.url.includes('m.js')) {
const linkHandler = (e) => {
e.preventDefault()
window.open(link.href)
};
link.addEventListener('click', linkHandler);
link.addEventListener('touchend', linkHandler);
}
link.textContent = ad.title;
const summary = adElement.querySelector('summary');
summary.textContent = ad.summary;
mainElement.appendChild(adElement);
}
29 changes: 29 additions & 0 deletions adClickFlow/server/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
async function routeInit () {
const express = require('express');
const routes = express.Router();

const { getPubUrl, getPubCompleteUrl } = await import('../shared/utils.mjs');
routes.get('/ad/aclick', (req, res) => {
const adPath = getPubUrl(req.query.ID, req.hostname);
res.redirect(302, adPath);
});

routes.get('/serp/y.js', (req, res) => {
res.redirect(302, decodeURIComponent(req.query.u));
});

routes.get('/serp/m.js', (req, res) => {
res.redirect(302, decodeURIComponent(req.query.u));
});

function redirectToPubComplete (req, res) {
const pubCompleteUrl = getPubCompleteUrl(req.hostname);
res.redirect(302, pubCompleteUrl);
}

routes.post('/pay/process.html', redirectToPubComplete);
routes.post('/pub/process.html', redirectToPubComplete);
return routes;
}

exports.init = routeInit;
Loading