From dcc7c6b10f45943a7f65bc1566953567c70c9d96 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Mon, 19 Oct 2020 14:16:33 +0300 Subject: [PATCH 01/13] Add Geoedge RTD provider submodule * Add Geoedge RTD provider submodule accroding to RTD phase 3 * Add tests * Add docs * Add integration example * Add as child module of RTD for easier builds --- .../gpt/geoedgeRtdProvider_example.html | 105 ++++++++ modules/.submodules.json | 3 +- modules/geoedgeRtdProvider.js | 243 ++++++++++++++++++ modules/geoedgeRtdProvider.md | 58 +++++ test/spec/modules/geoedgeRtdProvider_spec.js | 112 ++++++++ 5 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 integrationExamples/gpt/geoedgeRtdProvider_example.html create mode 100644 modules/geoedgeRtdProvider.js create mode 100644 modules/geoedgeRtdProvider.md create mode 100644 test/spec/modules/geoedgeRtdProvider_spec.js diff --git a/integrationExamples/gpt/geoedgeRtdProvider_example.html b/integrationExamples/gpt/geoedgeRtdProvider_example.html new file mode 100644 index 00000000000..e08b1abc5ce --- /dev/null +++ b/integrationExamples/gpt/geoedgeRtdProvider_example.html @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + \ No newline at end of file diff --git a/modules/.submodules.json b/modules/.submodules.json index 91cda9d95ad..3fa5d48848d 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -26,6 +26,7 @@ "rtdModule": [ "browsiRtdProvider", "audigentRtdProvider", - "jwplayerRtdProvider" + "jwplayerRtdProvider", + "geoedgeRtdProvider" ] } diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js new file mode 100644 index 00000000000..db322d779a8 --- /dev/null +++ b/modules/geoedgeRtdProvider.js @@ -0,0 +1,243 @@ +/** + * 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'; +/** @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; +} + +/** + * 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; + return wrapperReady && supportedBidder && wap; +} + +function conditionallyWrap(bid) { + if (shouldWrap(bid)) { + wrapBidResponse(bid); + } +} + +function init(config, gdpr, usp) { + return true; +} + +export function getData(adUnits, onDone) { + onDone({}); +} + +/** @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 + */ + getData, + init, + updateBidResponse: conditionallyWrap +}; + +/** + * sets the module params from config + * @param {Object} realTimeData + */ +export function setParams(realTimeData) { + 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 }) => { + setParams(realTimeData); + unsubscribe(); +}); + +submodule('realTimeData', geoedgeSubmodule); diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md new file mode 100644 index 00000000000..a8f9e2be196 --- /dev/null +++ b/modules/geoedgeRtdProvider.md @@ -0,0 +1,58 @@ +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 for publishers:** + +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 + 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 + } + } + }] + } +}); +``` + +**Usage for bidder adapters:** + +Bid adapter must import the geoedge module and pass thier bid to the wrapBidResponse method inside their interpretResponse logic. This method will mutate the bid.ad property. + +```javascript +import { fetchWrapper, wrapBidResponse, preloadClient } from './geoedgeRtdProvider.js'; +fetchWrapper(wrapper => wrapBidResponse(bidResponse, key)); + +``` + +Bid adapter could call perloadClient method when thier onBidWon is called. + +```javascript +onBidWon: function () { + //... + preloadClient(key); +}; + +``` + +**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` diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js new file mode 100644 index 00000000000..4d43a387cac --- /dev/null +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -0,0 +1,112 @@ +import { config } from '../../../src/config.js'; +import * as utils from '../../../src/utils.js'; +import * as geoedge from '../../../modules/geoedgeRtdProvider.js'; +import { server } from '../../../test/mocks/xhr.js'; + +let key = '123123123'; +function makeConfig () { + return { + dataProviders: [{ + name: 'geoedge', + params: { + wap: false, + key: key, + bidders: { + bidderA: true, + bidderB: false + } + } + }] + }; +} + +function mockBid(bidderCode) { + return { + 'ad': '', + 'cpm': '1.00', + 'width': 300, + 'height': 250, + 'bidderCode': bidderCode, + 'requestId': utils.getUniqueIdentifierStr(), + 'creativeId': 'id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }; +} + +let mockWrapper = `${geoedge.htmlPlaceholder}`; + +describe('Geoedge RTD module', function () { + describe('before init', function () { + let insertElementStub; + + before(function () { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + after(function () { + utils.insertElement.restore(); + }); + it('should not fetch the wrapper if missing params', function () { + var missingConf = makeConfig(); + delete missingConf.dataProviders[0].params; + geoedge.setParams(missingConf); + let request = server.requests[0]; + expect(request).to.equal(undefined); + }); + it('should fetch the wrapper if params present', function () { + geoedge.setParams(makeConfig()); + let request = server.requests[0]; + let isWrapperRequest = request && request.url && request.url && request.url === geoedge.WRAPPER_URL; + expect(isWrapperRequest).to.equal(true); + }); + it('should preload the client', function () { + let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href.indexOf(geoedge.getClientUrl(key)) > 4; + expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); + }); + }); + describe('setWrapper', function () { + it('should set the wrapper', function () { + geoedge.setWrapper(mockWrapper); + expect(geoedge.wrapper).to.equal(mockWrapper); + }); + }); + describe('submodule', function () { + describe('name', function () { + it('should be geoedge', function () { + expect(geoedge.geoedgeSubmodule.name).to.equal('geoedge'); + }); + }); + describe('getData', function () { + it('should call onDone with an empty object', function (done) { + geoedge.geoedgeSubmodule.getData([], data => { + expect(utils.isPlainObject(data) && utils.isEmpty(data)).to.equal(true); + done(); + }); + }); + }); + describe('init', function () { + it('should return true', function () { + expect(geoedge.geoedgeSubmodule.init()).to.equal(true); + }); + }); + describe('updateBidResponse', function () { + let bidFromA = mockBid('bidderA'); + it('should wrap bid html when bidder is configured', function () { + geoedge.geoedgeSubmodule.updateBidResponse(bidFromA); + expect(bidFromA.ad.indexOf('')).to.equal(0); + }); + it('should not wrap bid html when bidder is not configured', function () { + let bidFromB = mockBid('bidderB'); + geoedge.geoedgeSubmodule.updateBidResponse(bidFromB); + expect(bidFromB.ad.indexOf('')).to.equal(-1); + }); + it('should only muatate the bid ad porperty', function () { + let copy = Object.assign({}, bidFromA); + delete copy.ad; + let equalsOriginal = Object.keys(copy).every(key => copy[key] === bidFromA[key]); + expect(equalsOriginal).to.equal(true); + }); + }); + }); +}); From 98916ce2b5eb4f95a263c1e48f59976df6b4dc2e Mon Sep 17 00:00:00 2001 From: daniel manan Date: Wed, 4 Nov 2020 20:50:48 +0200 Subject: [PATCH 02/13] Update RTD submodule interface See https://docs.prebid.org/dev-docs/add-rtd-submodule.html --- modules/geoedgeRtdProvider.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index db322d779a8..133148bf59a 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -193,10 +193,6 @@ function init(config, gdpr, usp) { return true; } -export function getData(adUnits, onDone) { - onDone({}); -} - /** @type {RtdSubmodule} */ export const geoedgeSubmodule = { /** @@ -210,9 +206,8 @@ export const geoedgeSubmodule = { * @param {adUnit[]} adUnits * @param {function} onDone */ - getData, init, - updateBidResponse: conditionallyWrap + onBidResponseEvent: conditionallyWrap }; /** From 74ba8dabef140b09d507ddb4d8b857d3a5535829 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Wed, 4 Nov 2020 20:52:08 +0200 Subject: [PATCH 03/13] Update tests --- test/spec/modules/geoedgeRtdProvider_spec.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index 4d43a387cac..0c0901b4df1 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -77,28 +77,20 @@ describe('Geoedge RTD module', function () { expect(geoedge.geoedgeSubmodule.name).to.equal('geoedge'); }); }); - describe('getData', function () { - it('should call onDone with an empty object', function (done) { - geoedge.geoedgeSubmodule.getData([], data => { - expect(utils.isPlainObject(data) && utils.isEmpty(data)).to.equal(true); - done(); - }); - }); - }); describe('init', function () { it('should return true', function () { expect(geoedge.geoedgeSubmodule.init()).to.equal(true); }); }); - describe('updateBidResponse', function () { + describe('onBidResponseEvent', function () { let bidFromA = mockBid('bidderA'); it('should wrap bid html when bidder is configured', function () { - geoedge.geoedgeSubmodule.updateBidResponse(bidFromA); + geoedge.geoedgeSubmodule.onBidResponseEvent(bidFromA); expect(bidFromA.ad.indexOf('')).to.equal(0); }); it('should not wrap bid html when bidder is not configured', function () { let bidFromB = mockBid('bidderB'); - geoedge.geoedgeSubmodule.updateBidResponse(bidFromB); + geoedge.geoedgeSubmodule.onBidResponseEvent(bidFromB); expect(bidFromB.ad.indexOf('')).to.equal(-1); }); it('should only muatate the bid ad porperty', function () { From a643f2b25bf4b5c0b875d668290c0ee98876966a Mon Sep 17 00:00:00 2001 From: daniel manan Date: Wed, 4 Nov 2020 20:52:46 +0200 Subject: [PATCH 04/13] Update integration example remove unnecessary param --- integrationExamples/gpt/geoedgeRtdProvider_example.html | 1 - 1 file changed, 1 deletion(-) diff --git a/integrationExamples/gpt/geoedgeRtdProvider_example.html b/integrationExamples/gpt/geoedgeRtdProvider_example.html index e08b1abc5ce..27fdfa2b710 100644 --- a/integrationExamples/gpt/geoedgeRtdProvider_example.html +++ b/integrationExamples/gpt/geoedgeRtdProvider_example.html @@ -51,7 +51,6 @@ dataProviders: [{ name: 'geoedge', params: { - enabled: true, key: 'b954f7bb-9e26-427e-90f7-0c617382176a', bidders: { 'appnexus': true From 5afa5ecd3f4150ae41ebc5118a0603d8a67c2f57 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Wed, 4 Nov 2020 20:53:34 +0200 Subject: [PATCH 05/13] Update docs --- modules/geoedgeRtdProvider.md | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md index a8f9e2be196..e17a5ab1146 100644 --- a/modules/geoedgeRtdProvider.md +++ b/modules/geoedgeRtdProvider.md @@ -1,6 +1,6 @@ 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 for publishers:** +**Usage:** Compile the RTD Provider into your Prebid build: @@ -25,26 +25,6 @@ pbjs.setConfig({ }); ``` -**Usage for bidder adapters:** - -Bid adapter must import the geoedge module and pass thier bid to the wrapBidResponse method inside their interpretResponse logic. This method will mutate the bid.ad property. - -```javascript -import { fetchWrapper, wrapBidResponse, preloadClient } from './geoedgeRtdProvider.js'; -fetchWrapper(wrapper => wrapBidResponse(bidResponse, key)); - -``` - -Bid adapter could call perloadClient method when thier onBidWon is called. - -```javascript -onBidWon: function () { - //... - preloadClient(key); -}; - -``` - **Example:** To view an example: From a71ad6e7d349ddc9c887631b26fe01ef5271efeb Mon Sep 17 00:00:00 2001 From: daniel manan Date: Thu, 5 Nov 2020 18:47:32 +0200 Subject: [PATCH 06/13] Update RTD submodule provider * Remove getConfig * Get params from init * Use beforeInit --- modules/geoedgeRtdProvider.js | 69 +++++++++++------------------------ 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 133148bf59a..fc46c51d5af 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -16,13 +16,9 @@ */ 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} */ @@ -36,8 +32,6 @@ const PV_ID = generateUUID(); 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} */ @@ -101,7 +95,7 @@ export function wrapHtml(wrapper, html) { * @param {string} key * @return {Object} */ -function getMacros(bid, key = providerParams.key) { +function getMacros(bid, key) { return { '${key}': key, '%%ADUNIT%%': bid.adUnitCode, @@ -158,7 +152,7 @@ function mutateBid(bid, ad) { * @param {Object} bid * @param {string} key */ -export function wrapBidResponse(bid, key = providerParams.key) { +export function wrapBidResponse(bid, key) { let wrapped = buildHtml(bid, wrapper, bid.ad, key); mutateBid(bid, wrapped); } @@ -168,8 +162,8 @@ export function wrapBidResponse(bid, key = providerParams.key) { * @param {string} bidder * @return {boolean} */ -function isSupportedBidder(bidder) { - return isEmpty(providerParams.bidders) || providerParams.bidders[bidder] === true; +function isSupportedBidder(bidder, paramsBidders) { + return isEmpty(paramsBidders) || paramsBidders[bidder] === true; } /** @@ -177,19 +171,26 @@ function isSupportedBidder(bidder) { * @param {Object} bid * @return {boolean} */ -function shouldWrap(bid) { - let supportedBidder = isSupportedBidder(bid.bidderCode); - let wap = providerParams.wap ? preloaded : true; +function shouldWrap(bid, params) { + let supportedBidder = isSupportedBidder(bid.bidderCode, params.bidders); + let wap = params.wap ? preloaded : true; return wrapperReady && supportedBidder && wap; } -function conditionallyWrap(bid) { - if (shouldWrap(bid)) { - wrapBidResponse(bid); +function conditionallyWrap(bidResponse, config, userConsent) { + let params = config.params; + if (shouldWrap(bidResponse, params)) { + wrapBidResponse(bidResponse, params.key); } } -function init(config, gdpr, usp) { +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; } @@ -200,39 +201,13 @@ export const geoedgeSubmodule = { * @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) { - 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); - } +export function beforeInit() { + fetchWrapper(setWrapper); + submodule('realTimeData', geoedgeSubmodule); } -let unsubscribe = config.getConfig(MODULE_NAME, ({ realTimeData }) => { - setParams(realTimeData); - unsubscribe(); -}); - -submodule('realTimeData', geoedgeSubmodule); +beforeInit(); From feabbc06c8ab6a5c654d7d86e2916ff69cd7a123 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Thu, 5 Nov 2020 18:49:15 +0200 Subject: [PATCH 07/13] Update docs Extend and document the wap param --- modules/geoedgeRtdProvider.md | 63 +++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md index e17a5ab1146..e4aa046a97d 100644 --- a/modules/geoedgeRtdProvider.md +++ b/modules/geoedgeRtdProvider.md @@ -1,38 +1,67 @@ -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) +## Overview -**Usage:** +Module Name: Geoedge Rtd provider +Module Type: Rtd Provider +Maintainer: guy.books@geoedge.com -Compile the RTD Provider into your Prebid build: +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. -`gulp build --modules=geoedgeRtdProvider` +## Integration -Publishers must register Geoedge as a real time data provider by setting it up as a data provider in their realTimeData config: +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: [{ - geoedge: { - key: '123123', // Required, contact Geoedge to get your key - bidders: { // Optional, list of bidder to include / exclude from monitoring. Omitting this will monitor bids from all bidders + name: 'geoedge', + params: { + key: '123123', + 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 - } + 'bidderB': false // do not monitor bids form this bidder. + }, + wap: true } }] } }); ``` -**Example:** +Parameters details: -To view an example: +{: .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: -- in your cli run: +1) in your cli run: -`gulp serve --modules=appnexusBidAdapter,geoedgeRtdProvider` +``` +gulp serve --modules=appnexusBidAdapter,geoedgeRtdProvider +``` -- in your browser, navigate to: +2) in your browser, navigate to: -`http://localhost:9999/integrationExamples/gpt/geoedgeRtdProvider_example.html` +``` +http://localhost:9999/integrationExamples/gpt/geoedgeRtdProvider_example.html +``` From 8e8e9feccf7a11daa737045efa474ab8f88bf436 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Thu, 5 Nov 2020 18:49:51 +0200 Subject: [PATCH 08/13] Update tests --- test/spec/modules/geoedgeRtdProvider_spec.js | 82 +++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index 0c0901b4df1..3052b638c0c 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -1,22 +1,21 @@ import { config } from '../../../src/config.js'; import * as utils from '../../../src/utils.js'; -import * as geoedge from '../../../modules/geoedgeRtdProvider.js'; +import * as hook from '../../../src/hook.js' +import { beforeInit, geoedgeSubmodule, setWrapper, wrapper, htmlPlaceholder, WRAPPER_URL, getClientUrl } from '../../../modules/geoedgeRtdProvider.js'; import { server } from '../../../test/mocks/xhr.js'; let key = '123123123'; -function makeConfig () { +function makeConfig() { return { - dataProviders: [{ - name: 'geoedge', - params: { - wap: false, - key: key, - bidders: { - bidderA: true, - bidderB: false - } + name: 'geoedge', + params: { + wap: false, + key: key, + bidders: { + bidderA: true, + bidderB: false } - }] + } }; } @@ -35,62 +34,71 @@ function mockBid(bidderCode) { }; } -let mockWrapper = `${geoedge.htmlPlaceholder}`; +let mockWrapper = `${htmlPlaceholder}`; describe('Geoedge RTD module', function () { - describe('before init', function () { - let insertElementStub; + describe('beforeInit', function () { + let submoduleStub; before(function () { - insertElementStub = sinon.stub(utils, 'insertElement'); + submoduleStub = sinon.stub(hook, 'submodule'); }); after(function () { - utils.insertElement.restore(); + submoduleStub.restore(); }); - it('should not fetch the wrapper if missing params', function () { - var missingConf = makeConfig(); - delete missingConf.dataProviders[0].params; - geoedge.setParams(missingConf); + it('should fetch the wrapper', function () { + beforeInit(); let request = server.requests[0]; - expect(request).to.equal(undefined); - }); - it('should fetch the wrapper if params present', function () { - geoedge.setParams(makeConfig()); - let request = server.requests[0]; - let isWrapperRequest = request && request.url && request.url && request.url === geoedge.WRAPPER_URL; + let isWrapperRequest = request && request.url && request.url && request.url === WRAPPER_URL; expect(isWrapperRequest).to.equal(true); }); - it('should preload the client', function () { - let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href.indexOf(geoedge.getClientUrl(key)) > 4; - expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); + it('should register RTD submodule provider', function () { + expect(submoduleStub.calledWith('realTimeData', geoedgeSubmodule)).to.equal(true); }); }); describe('setWrapper', function () { it('should set the wrapper', function () { - geoedge.setWrapper(mockWrapper); - expect(geoedge.wrapper).to.equal(mockWrapper); + setWrapper(mockWrapper); + expect(wrapper).to.equal(mockWrapper); }); }); describe('submodule', function () { describe('name', function () { it('should be geoedge', function () { - expect(geoedge.geoedgeSubmodule.name).to.equal('geoedge'); + expect(geoedgeSubmodule.name).to.equal('geoedge'); }); }); describe('init', function () { - it('should return true', function () { - expect(geoedge.geoedgeSubmodule.init()).to.equal(true); + let insertElementStub; + + before(function () { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + after(function () { + utils.insertElement.restore(); + }); + it('should return false when missing params or key', function () { + let missingParams = geoedgeSubmodule.init({}); + let missingKey = geoedgeSubmodule.init({ params: {} }); + expect(missingParams || missingKey).to.equal(false); + }); + it('should return true when params are ok', function () { + expect(geoedgeSubmodule.init(makeConfig())).to.equal(true); + }); + it('should preload the client', function () { + let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href.indexOf(getClientUrl(key)) > 4; + expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); }); }); describe('onBidResponseEvent', function () { let bidFromA = mockBid('bidderA'); it('should wrap bid html when bidder is configured', function () { - geoedge.geoedgeSubmodule.onBidResponseEvent(bidFromA); + geoedgeSubmodule.onBidResponseEvent(bidFromA, makeConfig()); expect(bidFromA.ad.indexOf('')).to.equal(0); }); it('should not wrap bid html when bidder is not configured', function () { let bidFromB = mockBid('bidderB'); - geoedge.geoedgeSubmodule.onBidResponseEvent(bidFromB); + geoedgeSubmodule.onBidResponseEvent(bidFromB, makeConfig()); expect(bidFromB.ad.indexOf('')).to.equal(-1); }); it('should only muatate the bid ad porperty', function () { From 06a9ac62a2a5ba58efcc99aa3fbaf460c9adf8f7 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Thu, 5 Nov 2020 19:22:02 +0200 Subject: [PATCH 09/13] Remove unused config module --- test/spec/modules/geoedgeRtdProvider_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index 3052b638c0c..36e6729944d 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -1,4 +1,3 @@ -import { config } from '../../../src/config.js'; import * as utils from '../../../src/utils.js'; import * as hook from '../../../src/hook.js' import { beforeInit, geoedgeSubmodule, setWrapper, wrapper, htmlPlaceholder, WRAPPER_URL, getClientUrl } from '../../../modules/geoedgeRtdProvider.js'; From b6d628c64f58776f570ff572691704e9ea966f8a Mon Sep 17 00:00:00 2001 From: daniel manan Date: Sun, 22 Nov 2020 22:38:46 +0200 Subject: [PATCH 10/13] Update integreation example Relevant opening and inline comments --- .../gpt/geoedgeRtdProvider_example.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/integrationExamples/gpt/geoedgeRtdProvider_example.html b/integrationExamples/gpt/geoedgeRtdProvider_example.html index 27fdfa2b710..0be2dfab0fe 100644 --- a/integrationExamples/gpt/geoedgeRtdProvider_example.html +++ b/integrationExamples/gpt/geoedgeRtdProvider_example.html @@ -1,10 +1,6 @@ @@ -46,13 +42,16 @@ pbjs.que.push(function() { pbjs.addAdUnits(adUnits); + // Call setConfig with Geoedge as RTD provider pbjs.setConfig({ realTimeData: { dataProviders: [{ name: 'geoedge', params: { + // Example key, contact Geoedge to get your key key: 'b954f7bb-9e26-427e-90f7-0c617382176a', bidders: { + // Replace 'appnexus' the bidder name you want to monitor, if you aren't using appnexus in your build 'appnexus': true } } From 28630a6f2cdb89dc0c1e91eabfe4afd02569cce3 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Sun, 22 Nov 2020 22:40:13 +0200 Subject: [PATCH 11/13] Update Geoedge RTD submodule provider * Hardcode HTTPS scheme * Rename to "donePreload" for clarity * Use regex to replace macros instead of loop --- modules/geoedgeRtdProvider.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index fc46c51d5af..001ef67b66a 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -22,14 +22,14 @@ 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'; +export const WRAPPER_URL = 'https://wrappers.geoedge.be/wrapper.html'; /** @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'; +const HOST_NAME = 'https://rumcdn.geoedge.be'; /** @type {string} */ const FILE_NAME = 'grumi.js'; /** @type {function} */ @@ -118,11 +118,11 @@ function getMacros(bid, key) { * @return {string} */ function replaceMacros(wrapper, macros) { - let key; - for (key in macros) { - wrapper = wrapper.replace(key, replacer(macros[key])); - } - return wrapper; + var re = new RegExp('\\' + Object.keys(macros).join('|'), 'gi'); + + return wrapper.replace(re, function(matched) { + return macros[matched]; + }); } /** @@ -173,8 +173,8 @@ function isSupportedBidder(bidder, paramsBidders) { */ function shouldWrap(bid, params) { let supportedBidder = isSupportedBidder(bid.bidderCode, params.bidders); - let wap = params.wap ? preloaded : true; - return wrapperReady && supportedBidder && wap; + let donePreload = params.wap ? preloaded : true; + return wrapperReady && supportedBidder && donePreload; } function conditionallyWrap(bidResponse, config, userConsent) { From 6a480870f7bb449f327796da3ac97929169aae63 Mon Sep 17 00:00:00 2001 From: daniel manan Date: Sun, 22 Nov 2020 22:42:22 +0200 Subject: [PATCH 12/13] Update tests Preload request scheme is now always HTTPS --- test/spec/modules/geoedgeRtdProvider_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index 36e6729944d..cf4e0b53fde 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -85,7 +85,7 @@ describe('Geoedge RTD module', function () { expect(geoedgeSubmodule.init(makeConfig())).to.equal(true); }); it('should preload the client', function () { - let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href.indexOf(getClientUrl(key)) > 4; + let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href === getClientUrl(key); expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); }); }); From 369701423e774139c38fe83d1b0f874f53d3172a Mon Sep 17 00:00:00 2001 From: daniel manan Date: Mon, 23 Nov 2020 18:13:08 +0200 Subject: [PATCH 13/13] Remove integration example HTML page As for @Fawke request at https://github.com/prebid/Prebid.js/pull/5869#issuecomment-732237482 --- .../gpt/geoedgeRtdProvider_example.html | 103 ------------------ 1 file changed, 103 deletions(-) delete mode 100644 integrationExamples/gpt/geoedgeRtdProvider_example.html diff --git a/integrationExamples/gpt/geoedgeRtdProvider_example.html b/integrationExamples/gpt/geoedgeRtdProvider_example.html deleted file mode 100644 index 0be2dfab0fe..00000000000 --- a/integrationExamples/gpt/geoedgeRtdProvider_example.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
- - \ No newline at end of file