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"
]
}
213 changes: 213 additions & 0 deletions modules/geoedgeRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/**
* 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 { ajax } from '../src/ajax.js';
import { generateUUID, insertElement, isEmpty, logError } from '../src/utils.js';

/** @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 {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) {
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) {
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, paramsBidders) {
return isEmpty(paramsBidders) || paramsBidders[bidder] === true;
}

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

function conditionallyWrap(bidResponse, config, userConsent) {
let params = config.params;
if (shouldWrap(bidResponse, params)) {
wrapBidResponse(bidResponse, params.key);
}
}

function init(config, userConsent) {
let params = config.params;
if (!params || !params.key) {
logError('missing key for geoedge RTD module provider');
return false;
}
preloadClient(params.key);
return true;
}

/** @type {RtdSubmodule} */
export const geoedgeSubmodule = {
/**
* used to link submodule with realTimeData
* @type {string}
*/
name: SUBMODULE_NAME,
init,
onBidResponseEvent: conditionallyWrap
};

export function beforeInit() {
fetchWrapper(setWrapper);
submodule('realTimeData', geoedgeSubmodule);
}

beforeInit();
67 changes: 67 additions & 0 deletions modules/geoedgeRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## Overview

Module Name: Geoedge Rtd provider
Module Type: Rtd Provider
Maintainer: guy.books@geoedge.com

The Geoedge Realtime module let pusblishers to block bad ads such as automatic redirects, malware, offensive creatives and landing pages.
To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and cutomer key.

## Integration

1) Build the geoedge RTD module into the Prebid.js package with:

```
gulp build --modules=geoedgeRtdProvider,...
```

2) Use `setConfig` to instruct Prebid.js to initilize the geoedge module, as specified below.

## Configuration

This module is configured as part of the `realTimeData.dataProviders` object:

```javascript
pbjs.setConfig({
realTimeData: {
dataProviders: [{
name: 'geoedge',
params: {
key: '123123',
bidders: {
'bidderA': true, // monitor bids form this bidder
'bidderB': false // do not monitor bids form this bidder.
},
wap: true
}
}]
}
});
```

Parameters details:

{: .table .table-bordered .table-striped }
|Name |Type |Description |Notes |
| :------------ | :------------ | :------------ |:------------ |
|name | String | Real time data module name |Required, always 'geoedge' |
|params | Object | | |
|params.key | String | Customer key |Required, contact Geoedge to get your key |
|params.bidders | Object | Bidders to monitor |Optional, list of bidder to include / exclude from monitoring. Omitting this will monitor bids from all bidders. |
|params.wap |Boolean |Wrap after preload |Optional, defaults to `false`. Set to `true` if you want to monitor only after the module has preloaded the monitoring client. |

## Example

To view an integration example:

1) in your cli run:

```
gulp serve --modules=appnexusBidAdapter,geoedgeRtdProvider
```

2) in your browser, navigate to:

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