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

Update Criteo bid adapter to Prebid 1.x #2370

Merged
merged 35 commits into from
May 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b6534c4
Convert Criteo adapter to bidderFactory
Spark-NF Oct 11, 2017
1f7d0fa
Add documentation for Prebid 1.0 Criteo adapter
Spark-NF Oct 11, 2017
dc36bd3
Add support for zone-matching bids on Prebid 1.0 Criteo adapter
Spark-NF Oct 11, 2017
f780bc5
Add unit tests to the Prebid 1.0 Criteo adapter
Spark-NF Oct 11, 2017
dd664a7
Explicit the fact that Criteo bids are net revenue
Spark-NF Oct 12, 2017
85b65ed
Pass currency in Criteo 1.0 adapter
Spark-NF Nov 28, 2017
ae7b2ca
Update Criteo adapter to use PublisherTag if present
Spark-NF Nov 28, 2017
fa076c8
Implement fastbid in prebid 1.0 criteo adapter
ahubertcriteo Dec 1, 2017
761a203
Pass the bid requests to the Criteo interpret method
Spark-NF Dec 7, 2017
6a8ea52
Add missing ttl and creativeId fields to Criteo bids
Spark-NF Dec 7, 2017
d4ff98e
Add 'native' support to the Criteo adapter
Spark-NF Dec 7, 2017
b7d85e7
Check that the Criteo adapter returned by PublisherTag is not empty
Spark-NF Dec 11, 2017
675d3ea
Fix 'assign to const' IE errors in Criteo native adapter
Spark-NF Dec 12, 2017
ac73344
Update criteo prebid adapter to reload publisher tag once auction is
ahubertcriteo Dec 12, 2017
78b01ec
Disable the PublisherTag event queue
Spark-NF Jan 25, 2018
4bfa905
Fix Criteo adapter on older Prebid versions not using response.body
Spark-NF Jan 26, 2018
fa787ba
Fix TypeError if FastBid is outdated
Spark-NF Jan 29, 2018
1b1a191
Remove the success variable in tryGetCriteoFastBid
Spark-NF Jan 29, 2018
6e34a4c
Fix events being overwritten with FastBid
Spark-NF Jan 29, 2018
36dec3b
Update PublisherTag loading comment
Spark-NF Feb 1, 2018
14090c8
Use adUnitCode as impid
Spark-NF Feb 2, 2018
fc2f2ac
Add events handlers in Criteo adapter to fix timeouts not treated as …
Spark-NF Mar 19, 2018
de434bd
Add handler for setTargeting event
Spark-NF Mar 29, 2018
d62e9a5
Move the registeredEvents set higher up to reduce the chances of race…
Spark-NF Mar 29, 2018
5e3534f
Fix UTests following recent Criteo adapter changes
Spark-NF Apr 9, 2018
c64ac12
Add comment linking to the PublisherTag unminified source
Spark-NF Apr 10, 2018
e2df580
Do not return a request in buildRequests on error
Spark-NF Apr 19, 2018
6d81ed4
Use loadExternalScript instead of loadScript
Spark-NF Apr 26, 2018
ebf7dbc
Use spec.onTimeout instead of registering an event handler
Spark-NF Apr 26, 2018
5d7fdc4
GDPR support in Criteo adapter (#4)
ahubertcriteo May 2, 2018
167f09a
Remove BID_WON and SET_TARGETING events from Criteo adapter
Spark-NF May 3, 2018
5b9c1cd
Update adapter version
jsfaure May 16, 2018
47654b7
Add support for multi-size in Criteo adapter
Spark-NF May 21, 2018
dcea248
Fix support for multi size in Criteo adapter
jsfaure May 23, 2018
597d8f7
Update adapterVersion to 7
jsfaure May 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 262 additions & 0 deletions modules/criteoBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import { loadExternalScript } from 'src/adloader';
import { registerBidder } from 'src/adapters/bidderFactory';
import { parse } from 'src/url';
import * as utils from 'src/utils';

const ADAPTER_VERSION = 7;
const BIDDER_CODE = 'criteo';
const CDB_ENDPOINT = '//bidder.criteo.com/cdb';
const CRITEO_VENDOR_ID = 91;
const INTEGRATION_MODES = {
'amp': 1,
};
const PROFILE_ID = 207;

// Unminified source code can be found in: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js
const PUBLISHER_TAG_URL = '//static.criteo.net/js/ld/publishertag.prebid.js';

/** @type {BidderSpec} */
export const spec = {
code: BIDDER_CODE,

/**
* @param {object} bid
* @return {boolean}
*/
isBidRequestValid: bid => (
!!(bid && bid.params && (bid.params.zoneId || bid.params.networkId))
),

/**
* @param {BidRequest[]} bidRequests
* @param {*} bidderRequest
* @return {ServerRequest}
*/
buildRequests: (bidRequests, bidderRequest) => {
let url;
let data;

// If publisher tag not already loaded try to get it from fast bid
if (!publisherTagAvailable()) {
window.Criteo = window.Criteo || {};
window.Criteo.usePrebidEvents = false;

tryGetCriteoFastBid();

// Reload the PublisherTag after the timeout to ensure FastBid is up-to-date and tracking done properly
setTimeout(() => {
loadExternalScript(PUBLISHER_TAG_URL, BIDDER_CODE);
}, bidderRequest.timeout);
}

if (publisherTagAvailable()) {
const adapter = new Criteo.PubTag.Adapters.Prebid(PROFILE_ID, ADAPTER_VERSION, bidRequests, bidderRequest);
url = adapter.buildCdbUrl();
data = adapter.buildCdbRequest();
} else {
const context = buildContext(bidRequests);
url = buildCdbUrl(context);
data = buildCdbRequest(context, bidRequests, bidderRequest);
}

if (data) {
return { method: 'POST', url, data, bidRequests };
}
},

/**
* @param {*} response
* @param {ServerRequest} request
* @return {Bid[]}
*/
interpretResponse: (response, request) => {
const body = response.body || response;

if (publisherTagAvailable()) {
const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(request);
if (adapter) {
return adapter.interpretResponse(body, request);
}
}

const bids = [];

if (body && body.slots && utils.isArray(body.slots)) {
body.slots.forEach(slot => {
const bidRequest = request.bidRequests.find(b => b.adUnitCode === slot.impid && (!b.params.zoneId || parseInt(b.params.zoneId) === slot.zoneid));
const bidId = bidRequest.bidId;
const bid = {
requestId: bidId,
cpm: slot.cpm,
currency: slot.currency,
netRevenue: true,
ttl: slot.ttl || 60,
creativeId: bidId,
width: slot.width,
height: slot.height,
}
if (slot.native) {
bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback);
} else {
bid.ad = slot.creative;
}
bids.push(bid);
});
}

return bids;
},

/**
* @param {TimedOutBid} timeoutData
*/
onTimeout: (timeoutData) => {
if (publisherTagAvailable()) {
const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(timeoutData.auctionId);
adapter.handleBidTimeout();
}
},
};

/**
* @return {boolean}
*/
function publisherTagAvailable() {
return typeof Criteo !== 'undefined' && Criteo.PubTag && Criteo.PubTag.Adapters && Criteo.PubTag.Adapters.Prebid;
}

/**
* @param {BidRequest[]} bidRequests
* @return {CriteoContext}
*/
function buildContext(bidRequests) {
const url = utils.getTopWindowUrl();
const queryString = parse(url).search;

const context = {
url: url,
debug: queryString['pbt_debug'] === '1',
noLog: queryString['pbt_nolog'] === '1',
integrationMode: undefined,
};

bidRequests.forEach(bidRequest => {
if (bidRequest.params.integrationMode) {
context.integrationMode = bidRequest.params.integrationMode;
}
})

return context;
}

/**
* @param {CriteoContext} context
* @return {string}
*/
function buildCdbUrl(context) {
let url = CDB_ENDPOINT;
url += '?profileId=' + PROFILE_ID;
url += '&av=' + String(ADAPTER_VERSION);
url += '&cb=' + String(Math.floor(Math.random() * 99999999999));

if (context.integrationMode in INTEGRATION_MODES) {
url += '&im=' + INTEGRATION_MODES[context.integrationMode];
}
if (context.debug) {
url += '&debug=1';
}
if (context.noLog) {
url += '&nolog=1';
}

return url;
}

/**
* @param {CriteoContext} context
* @param {BidRequest[]} bidRequests
* @return {*}
*/
function buildCdbRequest(context, bidRequests, bidderRequest) {
let networkId;
const request = {
publisher: {
url: context.url,
},
slots: bidRequests.map(bidRequest => {
networkId = bidRequest.params.networkId || networkId;
const slot = {
impid: bidRequest.adUnitCode,
transactionid: bidRequest.transactionId,
auctionId: bidRequest.auctionId,
sizes: bidRequest.sizes.map(size => size[0] + 'x' + size[1]),
};
if (bidRequest.params.zoneId) {
slot.zoneid = bidRequest.params.zoneId;
}
if (bidRequest.params.publisherSubId) {
slot.publishersubid = bidRequest.params.publisherSubId;
}
if (bidRequest.params.nativeCallback) {
slot.native = true;
}
return slot;
}),
};
if (networkId) {
request.publisher.networkid = networkId;
}
if (bidderRequest && bidderRequest.gdprConsent) {
request.gdprConsent = {
gdprApplies: !!(bidderRequest.gdprConsent.gdprApplies),
consentData: bidderRequest.gdprConsent.consentString,
consentGiven: !!(bidderRequest.gdprConsent.vendorData && bidderRequest.gdprConsent.vendorData.vendorConsents &&
bidderRequest.gdprConsent.vendorData.vendorConsents[ CRITEO_VENDOR_ID.toString(10) ]),
};
}
return request;
}

/**
* @param {string} id
* @param {*} payload
* @param {*} callback
* @return {string}
*/
function createNativeAd(id, payload, callback) {
// Store the callback and payload in a global object to be later accessed from the creative
window.criteo_prebid_native_slots = window.criteo_prebid_native_slots || {};
window.criteo_prebid_native_slots[id] = { callback, payload };

// The creative is in an iframe so we have to get the callback and payload
// from the parent window (doesn't work with safeframes)
Copy link
Member

Choose a reason for hiding this comment

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

do you have a postMessage solution that would work in safeFrames?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe, we will study this possibility and push it in a second iteration if feasable.

Copy link
Member

Choose a reason for hiding this comment

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

that's fine

return `<script type="text/javascript">
var win = window;
for (var i = 0; i < 10; ++i) {
win = win.parent;
if (win.criteo_prebid_native_slots) {
var responseSlot = win.criteo_prebid_native_slots["${id}"];
responseSlot.callback(responseSlot.payload);
break;
}
}
</script>`;
}

/**
* @return {boolean}
*/
function tryGetCriteoFastBid() {
try {
const fastBid = localStorage.getItem('criteo_fast_bid');
if (fastBid !== null) {
eval(fastBid); // eslint-disable-line no-eval
Copy link
Member

Choose a reason for hiding this comment

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

is there an alternative to using eval here? What's being done?

Copy link
Contributor

Choose a reason for hiding this comment

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

Our adapter relies on an external script (publishertag.js). Once loaded for the first time we put it into the local storage and eval it during the next calls to not reload it from the network and have a better performances.

Copy link
Member

Choose a reason for hiding this comment

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

very interesting. Going to discuss with the prebid team

Copy link
Member

Choose a reason for hiding this comment

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

Thinking about this some more, I don't think this is safe since you are blindly reading in content from localStorage, which can be overwritten by any script on page. Please remove.

Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed by mail the is the only way we can store our library in the local storage and then reuse it.

Copy link
Member

Choose a reason for hiding this comment

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

@ahubertcriteo
Do you have any stats that show this is faster than a modern browser caching of the parsed code? It doesn't really make sense to eval (parse) it twice.

Copy link
Contributor Author

@Spark-NF Spark-NF May 3, 2018

Choose a reason for hiding this comment

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

Yes, we made a lot of testing with our publishers, and results are very positive.

The time between the call to buildRequests/callBids and the call to the PublisherTag entry point (in this adapter new Criteo.PubTag.Adapters.Prebid) was reduced by 80 to 95%, going from a few hundreds milliseconds (sometimes more than 1s depending on the publisher) to a few milliseconds (usually around 10ms).

The number of timeouts as detected by Prebid was also halved.

Even if the file was cached by the browser, the fact that we're simply adding a <script> causes it to have a pretty low priority in the loading chain. Plus, it's asynchronous as opposed to the localStorage that is synchronous (and fast).

If you'd rather have the localStorage.setItem more explicit, we can update the adapter to do so. However, we'd need an API to do a GET and return the file's contents (the ajax one seems like it could do the job).

return true;
}
} catch (e) {
// Unable to get fast bid
}
return false;
}

registerBidder(spec);
27 changes: 27 additions & 0 deletions modules/criteoBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Overview

Module Name: Criteo Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]

# Description

Module that connects to Criteo's demand sources.

# Test Parameters
```
var adUnits = [
{
code: 'banner-ad-div',
sizes: [[300, 250], [728, 90]],
bids: [
{
bidder: 'criteo',
params: {
zoneId: 497747
}
}
]
}
];
```
Loading