diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 6f910632fbc..fdd7aa2f5eb 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -20,6 +20,8 @@ import { ajax } from '../src/ajax.js'; import { generateUUID, insertElement, isEmpty, logError } from '../src/utils.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +import { loadExternalScript } from '../src/adloader.js'; +import { auctionManager } from '../src/auctionManager.js'; /** @type {string} */ const SUBMODULE_NAME = 'geoedge'; @@ -33,9 +35,13 @@ const PV_ID = generateUUID(); /** @type {string} */ const HOST_NAME = 'https://rumcdn.geoedge.be'; /** @type {string} */ -const FILE_NAME = 'grumi.js'; +const FILE_NAME_CLIENT = 'grumi.js'; +/** @type {string} */ +const FILE_NAME_INPAGE = 'grumi-ip.js'; +/** @type {function} */ +export let getClientUrl = (key) => `${HOST_NAME}/${key}/${FILE_NAME_CLIENT}`; /** @type {function} */ -export let getClientUrl = (key) => `${HOST_NAME}/${key}/${FILE_NAME}`; +export let getInPageUrl = (key) => `${HOST_NAME}/${key}/${FILE_NAME_INPAGE}`; /** @type {string} */ export let wrapper /** @type {boolean} */; @@ -177,7 +183,8 @@ function isSupportedBidder(bidder, paramsBidders) { function shouldWrap(bid, params) { let supportedBidder = isSupportedBidder(bid.bidderCode, params.bidders); let donePreload = params.wap ? preloaded : true; - return wrapperReady && supportedBidder && donePreload; + let isGPT = params.gpt; + return wrapperReady && supportedBidder && donePreload && !isGPT; } function conditionallyWrap(bidResponse, config, userConsent) { @@ -187,31 +194,55 @@ function conditionallyWrap(bidResponse, config, userConsent) { } } +function isBillingMessage(data, params) { + return data.key === params.key && data.impression; +} + /** - * Fire billable events for applicable bids + * Fire billable events when our client sends a message + * Messages will be sent only when: + * a. applicable bids are wrapped + * b. our code laoded and executed sucesfully */ function fireBillableEventsForApplicableBids(params) { - events.on(CONSTANTS.EVENTS.BID_WON, function (winningBid) { - if (shouldWrap(winningBid, params)) { + window.addEventListener('message', function (message) { + let data = message.data; + if (isBillingMessage(data, params)) { + let winningBid = auctionManager.findBidByAdId(data.adId); events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { vendor: SUBMODULE_NAME, - billingId: generateUUID(), - type: 'impression', - transactionId: winningBid.transactionId, - auctionId: winningBid.auctionId, - bidId: winningBid.requestId + billingId: data.impressionId, + type: winningBid ? 'impression' : data.type, + transactionId: winningBid?.transactionId || data.transactionId, + auctionId: winningBid?.auctionId || data.auctionId, + bidId: winningBid?.requestId || data.requestId }); } }); } +/** + * Loads Geoedge in page script that monitors all ad slots created by GPT + * @param {Object} params + */ +function setupInPage(params) { + window.grumi = params; + window.grumi.fromPrebid = true; + loadExternalScript(getInPageUrl(params.key), SUBMODULE_NAME); +} + 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); + if (params.gpt) { + setupInPage(params); + } else { + fetchWrapper(setWrapper); + preloadClient(params.key); + } fireBillableEventsForApplicableBids(params); return true; } @@ -228,7 +259,6 @@ export const geoedgeSubmodule = { }; export function beforeInit() { - fetchWrapper(setWrapper); submodule('realTimeData', geoedgeSubmodule); } diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md index 5414606612c..cdf913b8893 100644 --- a/modules/geoedgeRtdProvider.md +++ b/modules/geoedgeRtdProvider.md @@ -5,7 +5,7 @@ Module Type: Rtd Provider Maintainer: guy.books@geoedge.com The Geoedge Realtime module lets publishers 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. +To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and customer key. ## Integration @@ -49,6 +49,7 @@ Parameters details: |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. | +|params.gpt |Boolean |Wrap all GPT ad slots |Optional, defaults to `false`. Set to `true` if you want to monitor all Google Publisher Tag ad slots, regaedless if the winning bid comes from Prebid or Google Ad Manager (Direct, Adx, Adesnse, Open Bidding, etc). | ## Example diff --git a/src/adloader.js b/src/adloader.js index fb4aa44e872..a87b930b7df 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -26,6 +26,7 @@ const _approvedLoadExternalJSList = [ 'airgrid', 'clean.io', 'a1Media', + 'geoedge', ] /** diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index eec1feff87a..2f2fc8e2775 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -1,12 +1,13 @@ import * as utils from '../../../src/utils.js'; +import { loadExternalScript } from '../../../src/adloader.js'; import * as hook from '../../../src/hook.js' -import { beforeInit, geoedgeSubmodule, setWrapper, wrapper, htmlPlaceholder, WRAPPER_URL, getClientUrl } from '../../../modules/geoedgeRtdProvider.js'; +import { beforeInit, geoedgeSubmodule, setWrapper, wrapper, htmlPlaceholder, WRAPPER_URL, getClientUrl, getInPageUrl } from '../../../modules/geoedgeRtdProvider.js'; import { server } from '../../../test/mocks/xhr.js'; import * as events from '../../../src/events.js'; import CONSTANTS from '../../../src/constants.json'; let key = '123123123'; -function makeConfig() { +function makeConfig(gpt) { return { name: 'geoedge', params: { @@ -15,7 +16,8 @@ function makeConfig() { bidders: { bidderA: true, bidderB: false - } + }, + gpt: gpt } }; } @@ -23,6 +25,7 @@ function makeConfig() { function mockBid(bidderCode) { return { 'ad': '', + 'adId': '1234', 'cpm': '1.00', 'width': 300, 'height': 250, @@ -35,6 +38,15 @@ function mockBid(bidderCode) { }; } +function mockMessageFromClient(key) { + return { + key, + impression: true, + adId: 1234, + type: 'impression' + }; +} + let mockWrapper = `${htmlPlaceholder}`; describe('Geoedge RTD module', function () { @@ -47,22 +59,11 @@ describe('Geoedge RTD module', function () { after(function () { submoduleStub.restore(); }); - it('should fetch the wrapper', function () { - beforeInit(); - let request = server.requests[0]; - let isWrapperRequest = request && request.url && request.url && request.url === WRAPPER_URL; - expect(isWrapperRequest).to.equal(true); - }); it('should register RTD submodule provider', function () { + beforeInit(); expect(submoduleStub.calledWith('realTimeData', geoedgeSubmodule)).to.equal(true); }); }); - describe('setWrapper', function () { - it('should set the wrapper', function () { - setWrapper(mockWrapper); - expect(wrapper).to.equal(mockWrapper); - }); - }); describe('submodule', function () { describe('name', function () { it('should be geoedge', function () { @@ -84,35 +85,54 @@ describe('Geoedge RTD module', function () { expect(missingParams || missingKey).to.equal(false); }); it('should return true when params are ok', function () { - expect(geoedgeSubmodule.init(makeConfig())).to.equal(true); + expect(geoedgeSubmodule.init(makeConfig(false))).to.equal(true); + }); + it('should fetch the wrapper', function () { + geoedgeSubmodule.init(makeConfig(false)); + let request = server.requests[0]; + 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 === getClientUrl(key); expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); }); - it('should emit billable events with applicable winning bids', function () { - let applicableBid = mockBid('bidderA'); - let nonApplicableBid = mockBid('bidderB'); + it('should emit billable events with applicable winning bids', function (done) { let counter = 0; events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, function (event) { if (event.vendor === 'geoedge' && event.type === 'impression') { counter += 1; } + expect(counter).to.equal(1); + done(); }); - events.emit(CONSTANTS.EVENTS.BID_WON, applicableBid); - events.emit(CONSTANTS.EVENTS.BID_WON, nonApplicableBid); - expect(counter).to.equal(1); + window.postMessage(mockMessageFromClient(key), '*'); + }); + it('should load the in page code when gpt params is true', function () { + geoedgeSubmodule.init(makeConfig(true)); + let isInPageUrl = arg => arg == getInPageUrl(key); + expect(loadExternalScript.calledWith(sinon.match(isInPageUrl))).to.equal(true); + }); + it('should set the window.grumi config object when gpt params is true', function () { + let hasGrumiObj = typeof window.grumi === 'object'; + expect(hasGrumiObj && window.grumi.key === key && window.grumi.fromPrebid).to.equal(true); + }); + }); + describe('setWrapper', function () { + it('should set the wrapper', function () { + setWrapper(mockWrapper); + expect(wrapper).to.equal(mockWrapper); }); }); describe('onBidResponseEvent', function () { let bidFromA = mockBid('bidderA'); it('should wrap bid html when bidder is configured', function () { - geoedgeSubmodule.onBidResponseEvent(bidFromA, makeConfig()); + geoedgeSubmodule.onBidResponseEvent(bidFromA, makeConfig(false)); expect(bidFromA.ad.indexOf('')).to.equal(0); }); it('should not wrap bid html when bidder is not configured', function () { let bidFromB = mockBid('bidderB'); - geoedgeSubmodule.onBidResponseEvent(bidFromB, makeConfig()); + geoedgeSubmodule.onBidResponseEvent(bidFromB, makeConfig(false)); expect(bidFromB.ad.indexOf('')).to.equal(-1); }); it('should only muatate the bid ad porperty', function () {