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

feat: rendering the winning ad #53

Merged
merged 9 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ As of this moment, the polyfill is intended to work within the Chrome browser at
};

// join an interest group
fledge.joinAdInterestGroup(options, 864000000).then(response => console.log(response));
fledge.joinAdInterestGroup(options, 864000000);

// leave an interest group
fledge.leaveAdInterestGroup(options).then(response => console.log(response));
fledge.leaveAdInterestGroup(options);
</script>
```

Expand All @@ -63,13 +63,26 @@ As of this moment, the polyfill is intended to work within the Chrome browser at
</script>
```

### Render the Ad

```html
<script type="module">
import { fledge } from "./node_modules/@magnite/fledge.polyfill/esm/index.js";

// ...run the auction; see above for full example
const auctionResults = await fledge.runAdAuction(options);

fledge.renderAd(id, auctionResults);
</script>
```

## Where to Find Documentation

The best way to find out what's available is to dig through source code, as each directory has a README file to describe each feature.

* [Auctions](./src/auctions/README.md)
* [Interest Groups](./src/interest-groups/README.md)
1. [Tagging Interest Groups](./src/interest-groups/README.md)
2. [Running an Auction](./src/auctions/README.md)
3. [Rendering the Winner](./src/render/README.md)

## How We Track Changes [![Keep a Changelog](https://img.shields.io/badge/Keep%20a%20Changelog-1.0.0-orange)](https://keepachangelog.com/en/1.0.0/)

Expand Down
5 changes: 3 additions & 2 deletions docs/auction/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<title>Fledge Auction Testing</title>
</head>
<body>
<div id="ad-slot-1"></div>
<script type="module">
import fledge from '../../dist/esm/index.js';

Expand All @@ -14,8 +15,8 @@
decision_logic_url: 'https://entertaining-glitter-bowler.glitch.me/score.js',
};

const auctionResults = await fledge.runAdAuction(options);
console.log({auctionResults});
const auctionResults = await fledge.runAdAuction(options, true);
await fledge.renderAd('#ad-slot-1', auctionResults, true);
</script>
</body>
</html>
8 changes: 2 additions & 6 deletions docs/interest-groups/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,10 @@
bidding_logic_url: 'https://dark-organic-appeal.glitch.me/bid.js',
};

fledge.joinAdInterestGroup(options, 2592000000).then(response => {
console.log({response});
});
await fledge.joinAdInterestGroup(options, 2592000000, true);

document.getElementById("leave-ig").onclick = function leaveGroup() {
fledge.leaveAdInterestGroup(options).then(response => {
console.log({response});
});
await fledge.leaveAdInterestGroup(options, true);
};
</script>
</body>
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@
"dist",
"src"
],
"keywords": [
"fledge",
"turtledove",
"sparrow",
"dovekey",
"parrrot",
"tern",
"advertising",
"w3c web advertising business group"
],
"repository": "https://github.com/MagniteEngineering/fledge.polyfill.git",
"bugs": "https://github.com/MagniteEngineering/fledge.polyfill/issues",
"releases": "https://github.com/MagniteEngineering/fledge.polyfill/releases",
Expand Down
39 changes: 30 additions & 9 deletions src/auction/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import echo from '../utils/console.js';
import db, { AUCTION_STORE, IG_STORE } from '../utils/db.js';
import validate from '../utils/validation.js';
import types from './types.js';
Expand All @@ -20,37 +21,57 @@ import {
* @example
* runAdAuction({ seller: 'foo', decision_logic_url: 'http://example.com/auction', interst_group_buyers: [ 'www.buyer.com' ] });
*/
export default async function runAdAuction (conf) {
export default async function runAdAuction (conf, debug = false) {
debug && echo.group('Fledge: Auction');
debug && echo.log('auction config:', conf);
validate.param(conf, 'object');
validate.hasRequiredKeys(conf, [ 'seller', 'decision_logic_url', 'interest_group_buyers' ]);
validate.hasInvalidOptionTypes(conf, types);

// console.info('get all interest groups');
debug && echo.info('getting all interest groups');
const interestGroups = await db.store.getAll(IG_STORE);
debug && echo.table(interestGroups);

// console.info('checking eligibility of buyers based on "interest_group_buyers"');
debug && echo.info('checking eligibility of buyers based on "interest_group_buyers"');
const eligible = getEligible(interestGroups, conf.interest_group_buyers);
debug && echo.table(eligible);
if (!eligible) {
debug && echo.error('No eligible interest group buyers found!');
return null;
}

// console.info('get all bids from each buyer');
debug && echo.info('getting all bids from each buyer');
const bids = await getBids(eligible, conf);
if (!bids.length) {
debug && echo.table(bids);
debug && echo.info('filtering out invalid bids');
const filteredBids = bids.filter(item => item);
debug && echo.table(filteredBids);
if (!filteredBids.length) {
debug && echo.error('No bids found!');
return null;
}

// console.info('get the winning bid');
const [ winner ] = await getScores(bids, conf);
debug && echo.info('getting all scores, filtering and sorting');
const [ winner ] = await getScores(filteredBids, conf);
debug && echo.log('winner:', winner);
if (!winner) {
debug && echo.error('No winner found!');
return null;
}

// console.info('creating an entry in the auction store');
const token = await db.store.add(AUCTION_STORE, { id: uuid(), ...winner });
debug && echo.info('creating an entry in the auction store');
const token = await db.store.add(AUCTION_STORE, {
id: uuid(),
origin: `${window.top.location.origin}${window.top.location.pathname}`,
timestamp: Date.now(),
...winner,
});
debug && echo.log('auction token:', token);
if (!token) {
debug && echo.error('No auction token found!');
return null;
}
debug && echo.groupEnd();

return token;
}
2 changes: 1 addition & 1 deletion src/auction/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const uuid = () => ([ 1e7 ] + -1e3 + -4e3 + -8e3 + -1e11)
* @param {array<String>} an array of strings
* @return {object} a JSON response
*/
export const getTrustedSignals = async (url, keys) => {
const getTrustedSignals = async (url, keys) => {
const hostname = `hostname=${window.top.location.hostname}`;

if (!(url && keys)) {
Expand Down
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import runAdAuction from './auction/index.js';
import joinAdInterestGroup from './interest-groups/join.js';
import leaveAdInterestGroup from './interest-groups/leave.js';
import renderAd from './render/index.js';

export default {
runAdAuction,
joinAdInterestGroup,
leaveAdInterestGroup,
renderAd,
runAdAuction,
};
20 changes: 14 additions & 6 deletions src/interest-groups/join.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import echo from '../utils/console.js';
import db, { IG_STORE } from '../utils/db.js';
import validate from '../utils/validation.js';
import types from './types.js';
Expand Down Expand Up @@ -25,7 +26,10 @@ const MAX_EXPIRATION = 2592000000;
* @example
* joinAdInterestGroup({ owner: 'foo', name: 'bar', bidding_logic_url: 'http://example.com/bid' }, 2592000000);
*/
export default async function joinAdInterestGroup (options, expiry) {
export default async function joinAdInterestGroup (options, expiry, debug = false) {
debug && echo.group('Fledge: Join an Interest Group');
debug && echo.log('interest group options:', options);
debug && echo.log('interest group expiration:', expiry);
validate.param(options, 'object');
validate.param(expiry, 'number');
validate.hasRequiredKeys(options, [ 'owner', 'name', 'bidding_logic_url' ]);
Expand All @@ -35,22 +39,26 @@ export default async function joinAdInterestGroup (options, expiry) {
throw new Error(`'expiry' is set past the allowed maximum value. You must provide an expiration that is less than or equal to ${MAX_EXPIRATION}.`);
}

// console.info('checking for an existing interest group');
debug && echo.info('checking for an existing interest group');
const group = await db.store.get(IG_STORE, getIGKey(options.owner, options.name));
debug && echo.table(group);
let id;
if (group) {
// console.info('updating a new interest group');
await db.store.put(IG_STORE, group, {
debug && echo.info('updating a new interest group');
id = await db.store.put(IG_STORE, group, {
_expired: Date.now() + expiry,
...options,
});
} else {
// console.info('creating a new interest group');
await db.store.add(IG_STORE, {
debug && echo.info('creating a new interest group');
id = await db.store.add(IG_STORE, {
_key: getIGKey(options.owner, options.name),
_expired: Date.now() + expiry,
...options,
});
}
debug && echo.log('interest group id:', id);
debug && echo.groupEnd();

return true;
}
9 changes: 7 additions & 2 deletions src/interest-groups/leave.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import echo from '../utils/console.js';
import db, { IG_STORE } from '../utils/db.js';
import validate from '../utils/validation.js';
import types from './types.js';
Expand All @@ -17,13 +18,17 @@ import {
* @example
* leaveAdInterestGroup({ owner: 'foo', name: 'bar', bidding_logic_url: 'http://example.com/bid' });
*/
export default async function leaveAdInterestGroup (group) {
export default async function leaveAdInterestGroup (group, debug = false) {
debug && echo.group('Fledge: Leave an Interest Group');
debug && echo.log('interest group:', group);
validate.param(group, 'object');
validate.hasRequiredKeys(group, [ 'owner', 'name' ]);
validate.hasInvalidOptionTypes(group, types);

// console.info('deleting an existing interest group');
debug && echo.info('deleting an existing interest group');
await db.store.delete(IG_STORE, getIGKey(group.owner, group.name));
debug && echo.log('interest group deleted');
debug && echo.groupEnd();

return true;
}
40 changes: 40 additions & 0 deletions src/render/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Render

## Install

```bash
$ npm install @magnite/fledge.polyfill
```

## Usage

```html
<script type="module">
import { fledge } from "./node_modules/@magnite/fledge.polyfill/esm/index.js";

// ...run the auction; see auctions docs for full example
const auctionResults = await fledge.runAdAuction(options);

fledge.renderAd(id, auctionResults);
</script>
```

## API

### renderAd(id, results)

Returns `null` if no winning bid is found. Returns a `Promise` with a token representation of the winning bids rendering URL.

If an invalid option is passed, then an `Error` will be thrown with a reason to help debug.

#### id

Type: [`<HTML ID>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id)

The `id` of the element that will be the target for which the ad will render.

#### results

Type: [`<String>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)

A token, or string, that represents the results from an auction run via the `fledge.runAdAuction` call.
60 changes: 60 additions & 0 deletions src/render/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import echo from '../utils/console.js';
import db, { AUCTION_STORE } from '../utils/db.js';
import validate from '../utils/validation.js';
import {
getTarget,
renderFrame,
} from './utils.js';

/*
* @function
* @name renderAd
* @description render an ad
* @author Newton <[email protected]>
* @param {string} selector - a string reprensenting a valid selector to find an element on the page
* @param {string} token - a string that represents the results from an auction run via the `fledge.runAdAuction` call
* @throws {Error} Any parameters passed are incorrect or an incorrect type
* @return {Promise<null | true>}
*
* @example
* renderAd('#ad-slot-1', '76941e71-2ed7-416d-9c55-36d07beff786');
*/
export default async function renderAd (selector, token, debug = false) {
debug && echo.group('Fledge: Render an Ad');
debug && echo.log('ad render selector:', selector);
debug && echo.log('ad render token:', token);
validate.param(selector, 'string');
validate.param(token, 'string');

debug && echo.info('checking that target exists on the page');
const target = getTarget(selector);
debug && echo.log('target:', target);
if (!target) {
throw new Error(`Target not found on the page! Please check that ${target} exists on the page.`);
}

debug && echo.info('checking that winning token exists');
const winner = await db.store.get(AUCTION_STORE, token);
debug && echo.log('winners token:', winner);
if (!(winner && winner.id === token)) {
throw new Error(`A token was not found! Token provided: ${token}`);
}

debug && echo.info('checking that winner to be rendered is on the same hostname as the auction');
if (winner?.origin !== `${window.top.location.origin}${window.top.location.pathname}`) {
debug && echo.error(`Attempting to render the winner on a location that doesn't match the auctions hostname`, { winner: winner.origin, auction: `${window.top.location.origin}${window.top.location.pathname}` });
throw new Error('Something went wrong! No ad was rendered.');
}
debug && echo.info('rendering an iframe with the winning ad');
renderFrame(target, winner);

debug && echo.info('checking that ad iframe actually rendered');
const ad = getTarget(`#fledge-auction-${token}`);
debug && echo.log('ads target:', ad);
if (!ad) {
throw new Error('Something went wrong! No ad was rendered.');
}
debug && echo.groupEnd();

return true;
}
33 changes: 33 additions & 0 deletions src/render/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* @function
* @name getTarget
* @description grab a DOM node based on a CSS style selector passed in
* @author Newton <[email protected]>
* @param {string} selector - a CSS style selector
* @throws {Error} if no target is found based on the selector provided
* @return {DOM Node} a DOM node found on the page
*/
export const getTarget = selector => document.querySelector(selector);

/*
* @function
* @name renderFrame
* @description renders an iFrame when given a target and source URL
* @author Newton <[email protected]>
* @param {DOM Node} target - a valid DOM node with which to append an iframe
* @param {object} source - a valid winning ad object to render within the iframe
* @throws {Error} if no source URL is found based on the selector provided
* @return {void}
*/
export const renderFrame = (target, source) => {
if (!source.bid?.render) {
throw new Error(`Something went wrong! No rendering URL was found.`);
}

const iframe = document.createElement('iframe');
iframe.id = `fledge-auction-${source.id}`;
iframe.src = source.bid?.render;
iframe.scrolling = 'no';
iframe.style.borderWidth = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what size is the iframe here? we might need to set the dimensions.

target.appendChild(iframe);
};
Loading