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

Add Geoedge RTD provider submodule #5869

Merged
merged 14 commits into from
Nov 24, 2020
104 changes: 104 additions & 0 deletions integrationExamples/gpt/geoedgeRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<!--
This page calls a single bidder for a single ad slot. It can be considered a "hello world" example for using
Prebid with the Google Publisher Tag.
It also makes a good test page for new adapter PR submissions. Simply set your server's Bid Params object in the
bids array inside the adUnits, and it will use your adapter to load an ad.
NOTE that many ad servers won't send back an ad if the URL is localhost... so you might need to
set an alias in your /etc/hosts file so that you can load this page from a different domain.
-->
GeoEdge-r-and-d marked this conversation as resolved.
Show resolved Hide resolved

<html>
<head>
<script async src="../../build/dev/prebid.js"></script>
<script async src="https://www.googletagservices.com/tag/js/gpt.js"></script>
<script>
var FAILSAFE_TIMEOUT = 3300;
var PREBID_TIMEOUT = 1000;

var adUnits = [{
code: 'div-gpt-ad-1460505748561-0',
mediaTypes: {
banner: {
sizes: [[300, 250], [300,600]],
}
},
// Replace this object to test a new Adapter!
bids: [{
bidder: 'appnexus',
params: {
placementId: 13144370
}
}]

}];

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];

</script>

<script>
var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function() {
googletag.pubads().disableInitialLoad();
});

pbjs.que.push(function() {
pbjs.addAdUnits(adUnits);
pbjs.setConfig({
realTimeData: {
dataProviders: [{
name: 'geoedge',
params: {
key: 'b954f7bb-9e26-427e-90f7-0c617382176a',
bidders: {
'appnexus': true
}
}
}]
}
});
pbjs.requestBids({
bidsBackHandler: sendAdserverRequest,
timeout: PREBID_TIMEOUT
});
});

function sendAdserverRequest() {
if (pbjs.adserverRequestSent) return;
pbjs.adserverRequestSent = true;
googletag.cmd.push(function() {
pbjs.que.push(function() {
pbjs.setTargetingForGPTAsync();
googletag.pubads().refresh();
});
});
}

setTimeout(function() {
sendAdserverRequest();
}, FAILSAFE_TIMEOUT);

</script>

<script>
googletag.cmd.push(function () {
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-gpt-ad-1460505748561-0').addService(googletag.pubads());

googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
</head>

<body>
<h2>Prebid.js Test</h2>
<h5>Div-1</h5>
<div id='div-gpt-ad-1460505748561-0'>
<script type='text/javascript'>
googletag.cmd.push(function() { googletag.display('div-gpt-ad-1460505748561-0'); });
</script>
</div>
</body>
</html>
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"browsiRtdProvider",
"audigentRtdProvider",
"jwplayerRtdProvider",
"reconciliationRtdProvider"
"reconciliationRtdProvider",
"geoedgeRtdProvider"
]
}
238 changes: 238 additions & 0 deletions modules/geoedgeRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/**
* This module adds geoedge provider to the real time data module
* The {@link module:modules/realTimeData} module is required
* The module will fetch creative wrapper from geoedge server
* The module will place geoedge RUM client on bid responses markup
* @module modules/geoedgeProvider
* @requires module:modules/realTimeData
*/

/**
* @typedef {Object} ModuleParams
* @property {string} key
* @property {?Object} bidders
* @property {?boolean} wap
* @property {?string} keyName
*/

import { submodule } from '../src/hook.js';
import { config } from '../src/config.js';
import { ajax } from '../src/ajax.js';
import { generateUUID, insertElement, isEmpty, logError } from '../src/utils.js';
import find from 'core-js-pure/features/array/find.js';

/** @type {string} */
const MODULE_NAME = 'realTimeData';
/** @type {string} */
const SUBMODULE_NAME = 'geoedge';
/** @type {string} */
export const WRAPPER_URL = '//wrappers.geoedge.be/wrapper.html';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, structure of the module is looking better. Will assign someone to look deeper at the javascript. Now we have to talk about this external code. In order to protect publisher pages, Global Module Rule #1 is No External Code. Looking at wrapper.html, it's pretty small, but it loads another file: https://rumcdn.geoedge.be/$%7Bkey%7D/grumi.js.

You're going to have to figure out how to live within this Module Rule, basically, all code that's loaded must be inspectable, disclosed, and locked to a specific version so we know when you've changed it. Since the whole point of GeoEdge is to help protect publishers, I suspect you'll understand why this rule exists. :-)

If wrapper.html may be more-or-less static, you might consider including that right in this code. grumi.js probaby depends on the publisher, so for that you're going to need to A) open source it and B) lock it to a version, e.g. grumi-v1.js

If grumi.js needs data (e.g. malware patterns), you might consider:

  • separate the code from the data. Loading data files is fine.
  • make grumi.js as static as possible and put your application logic server-side

/** @type {string} */
/* eslint-disable no-template-curly-in-string */
export const HTML_PLACEHOLDER = '${creative}';
/** @type {string} */
const PV_ID = generateUUID();
/** @type {string} */
const HOST_NAME = '//rumcdn.geoedge.be';
/** @type {string} */
const FILE_NAME = 'grumi.js';
/** @type {ModuleParams} */
let providerParams = {};
/** @type {function} */
export let getClientUrl = (key) => `${HOST_NAME}/${key}/${FILE_NAME}`;
/** @type {string} */
export let wrapper
/** @type {boolean} */;
let wrapperReady;
/** @type {boolean} */;
let preloaded;

/**
* fetches the creative wrapper
* @param {function} sucess - success callback
*/
export function fetchWrapper(success) {
if (wrapperReady) {
return success(wrapper);
}
ajax(WRAPPER_URL, success);
}

/**
* sets the wrapper and calls preload client
* @param {string} responseText
*/
export function setWrapper(responseText) {
wrapperReady = true;
wrapper = responseText;
}

/**
* preloads the client
* @param {string} key
*/
export function preloadClient(key) {
let link = document.createElement('link');
link.rel = 'preload';
link.as = 'script';
link.href = getClientUrl(key);
link.onload = () => { preloaded = true };
insertElement(link);
}

/**
* creates identity function for string replace without special replacement patterns
* @param {string} str
* @return {function}
*/
function replacer(str) {
return function () {
return str;
}
}

export function wrapHtml(wrapper, html) {
return wrapper.replace(HTML_PLACEHOLDER, replacer(html));
}

/**
* generate macros dictionary from bid response
* @param {Object} bid
* @param {string} key
* @return {Object}
*/
function getMacros(bid, key = providerParams.key) {
return {
'${key}': key,
'%%ADUNIT%%': bid.adUnitCode,
'%%WIDTH%%': bid.width,
'%%HEIGHT%%': bid.height,
'%%PATTERN:hb_adid%%': bid.adId,
'%%PATTERN:hb_bidder%%': bid.bidderCode,
'%_isHb!': true,
'%_hbcid!': bid.creativeId || '',
'%%PATTERN:hb_pb%%': bid.pbHg,
'%%SITE%%': location.hostname,
'%_pimp%': PV_ID
};
}

/**
* replace macro placeholders in a string with values from a dictionary
* @param {string} wrapper
* @param {Object} macros
* @return {string}
*/
function replaceMacros(wrapper, macros) {
let key;
for (key in macros) {
wrapper = wrapper.replace(key, replacer(macros[key]));
}
return wrapper;
}
GeoEdge-r-and-d marked this conversation as resolved.
Show resolved Hide resolved

/**
* build final creative html with creative wrapper
* @param {Object} bid
* @param {string} wrapper
* @param {string} html
* @return {string}
*/
function buildHtml(bid, wrapper, html, key) {
let macros = getMacros(bid, key);
wrapper = replaceMacros(wrapper, macros);
return wrapHtml(wrapper, html);
}

/**
* muatates the bid ad property
* @param {Object} bid
* @param {string} ad
*/
function mutateBid(bid, ad) {
bid.ad = ad;
}

/**
* wraps a bid object with the creative wrapper
* @param {Object} bid
* @param {string} key
*/
export function wrapBidResponse(bid, key = providerParams.key) {
let wrapped = buildHtml(bid, wrapper, bid.ad, key);
mutateBid(bid, wrapped);
}

/**
* checks if bidder's bids should be monitored
* @param {string} bidder
* @return {boolean}
*/
function isSupportedBidder(bidder) {
return isEmpty(providerParams.bidders) || providerParams.bidders[bidder] === true;
}

/**
* checks if bid should be monitored
* @param {Object} bid
* @return {boolean}
*/
function shouldWrap(bid) {
let supportedBidder = isSupportedBidder(bid.bidderCode);
let wap = providerParams.wap ? preloaded : true;
bretg marked this conversation as resolved.
Show resolved Hide resolved
return wrapperReady && supportedBidder && wap;
}

function conditionallyWrap(bid) {
bretg marked this conversation as resolved.
Show resolved Hide resolved
if (shouldWrap(bid)) {
wrapBidResponse(bid);
}
}

function init(config, gdpr, usp) {
return true;
}

/** @type {RtdSubmodule} */
export const geoedgeSubmodule = {
/**
* used to link submodule with realTimeData
* @type {string}
*/
name: SUBMODULE_NAME,
/**
* get data and send back to realTimeData module
* @function
* @param {adUnit[]} adUnits
* @param {function} onDone
*/
init,
onBidResponseEvent: conditionallyWrap
};

/**
* sets the module params from config
* @param {Object} realTimeData
*/
export function setParams(realTimeData) {
bretg marked this conversation as resolved.
Show resolved Hide resolved
let dataProviders = realTimeData && realTimeData.dataProviders;
let geoedge;
try {
geoedge = dataProviders && find(dataProviders, provider => provider.name && provider.name.toLowerCase() === SUBMODULE_NAME);
} catch (e) { }
let params = geoedge && geoedge.params;
if (!params || !params.key) {
logError('missing key for geoedge RTD module provider');
} else {
providerParams = params;
fetchWrapper(setWrapper);
preloadClient(params.key);
}
}

let unsubscribe = config.getConfig(MODULE_NAME, ({ realTimeData }) => {
bretg marked this conversation as resolved.
Show resolved Hide resolved
setParams(realTimeData);
unsubscribe();
});

submodule('realTimeData', geoedgeSubmodule);
38 changes: 38 additions & 0 deletions modules/geoedgeRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
The purpose of this Real Time Data Provider is to allow publishers to use Geoedge real user monitoring solution, which supports real time blocking of bad ads (redirects, malware, offensive content, etc)

**Usage:**

Compile the RTD Provider into your Prebid build:

`gulp build --modules=geoedgeRtdProvider`

Publishers must register Geoedge as a real time data provider by setting it up as a data provider in their realTimeData config:

```javascript
pbjs.setConfig({
...,
realTimeData: {
dataProviders: [{
geoedge: {
key: '123123', // Required, contact Geoedge to get your key
bretg marked this conversation as resolved.
Show resolved Hide resolved
bidders: { // Optional, list of bidder to include / exclude from monitoring. Omitting this will monitor bids from all bidders
'bidderA': true, // monitor bids form this bidder
'bidderB': false // do not monitor bids form this bidder. Optional, omitting this entirely will have the same effect
}
}
}]
}
});
```

**Example:**

To view an example:

- in your cli run:

`gulp serve --modules=appnexusBidAdapter,geoedgeRtdProvider`

- in your browser, navigate to:

`http://localhost:9999/integrationExamples/gpt/geoedgeRtdProvider_example.html`
Loading