Skip to content

Commit

Permalink
Geoedge RTD module: support monitoring all GPT ad slots (#10291)
Browse files Browse the repository at this point in the history
* Added params.gpt config, to enable monitoring all GPT ad slots

* Update tests

* Update docs

* Add geoedge to external js list

* Fix test

---------

Co-authored-by: daniel manan <mmndaniel@gmail.com>
  • Loading branch information
GeoEdge-r-and-d and mmndaniel authored Aug 9, 2023
1 parent 4e0a780 commit b534649
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 38 deletions.
56 changes: 43 additions & 13 deletions modules/geoedgeRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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} */;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand All @@ -228,7 +259,6 @@ export const geoedgeSubmodule = {
};

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

Expand Down
3 changes: 2 additions & 1 deletion modules/geoedgeRtdProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/adloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const _approvedLoadExternalJSList = [
'airgrid',
'clean.io',
'a1Media',
'geoedge',
]

/**
Expand Down
68 changes: 44 additions & 24 deletions test/spec/modules/geoedgeRtdProvider_spec.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -15,14 +16,16 @@ function makeConfig() {
bidders: {
bidderA: true,
bidderB: false
}
},
gpt: gpt
}
};
}

function mockBid(bidderCode) {
return {
'ad': '<creative/>',
'adId': '1234',
'cpm': '1.00',
'width': 300,
'height': 250,
Expand All @@ -35,6 +38,15 @@ function mockBid(bidderCode) {
};
}

function mockMessageFromClient(key) {
return {
key,
impression: true,
adId: 1234,
type: 'impression'
};
}

let mockWrapper = `<wrapper>${htmlPlaceholder}</wrapper>`;

describe('Geoedge RTD module', function () {
Expand All @@ -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 () {
Expand All @@ -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('<wrapper>')).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('<wrapper>')).to.equal(-1);
});
it('should only muatate the bid ad porperty', function () {
Expand Down

0 comments on commit b534649

Please sign in to comment.