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

updated ozone adapter from v1.4.4 -> v2.0.0 #3828

Merged
merged 4 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
254 changes: 187 additions & 67 deletions modules/ozoneBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import * as utils from '../src/utils';
import { registerBidder } from '../src/adapters/bidderFactory';
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes';
import {config} from '../src/config';
import {getPriceBucketString} from '../src/cpmBucketManager';
import { Renderer } from '../src/Renderer'

const BIDDER_CODE = 'ozone';

const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction';
const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'

const OZONECOOKIESYNC = 'https://elb.the-ozone-project.com/static/load-cookie.html';
const OZONEVERSION = '1.4.7';
const OZONEVERSION = '2.0.0';

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [VIDEO, BANNER],

/**
* Basic check to see whether required parameters are in the request.
Expand Down Expand Up @@ -63,19 +69,26 @@ export const spec = {
return false;
}
}
if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) {
if (!bid.mediaTypes.video.hasOwnProperty('context')) {
utils.logInfo('OZONE: [WARNING] No context key/value in bid. Rejecting bid: ', ozoneBidRequest);
return false;
}
if (bid.mediaTypes.video.context !== 'outstream') {
utils.logInfo('OZONE: [WARNING] Only outstream video is supported. Rejecting bid: ', ozoneBidRequest);
return false;
}
}
return true;
},

buildRequests(validBidRequests, bidderRequest) {
utils.logInfo('OZONE: ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest);
utils.logInfo('OZONE: buildRequests setting auctionId', bidderRequest.auctionId);
let singleRequest = config.getConfig('ozone.singleRequest');

singleRequest = singleRequest !== false; // undefined & true will be true
utils.logInfo('OZONE: config ozone.singleRequest : ', singleRequest);
let htmlParams = validBidRequests[0].params; // the html page config params will be included in each element
let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params
// ozoneRequest['id'] = utils.generateUUID();

delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug']
if (bidderRequest.gdprConsent) {
utils.logInfo('OZONE: ADDING GDPR info');
Expand All @@ -92,8 +105,7 @@ export const spec = {
ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight};
let tosendtags = validBidRequests.map(ozoneBidRequest => {
var obj = {};
obj.id = ozoneBidRequest.bidId; // this causes a failure if we change it to something else
// obj.id = ozoneBidRequest.adUnitCode; // (eg. 'mpu' or 'leaderboard') A unique identifier for this impression within the context of the bid request (typically, starts with 1 and increments.
obj.id = ozoneBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder ozone made bid for unknown request ID: mb7953.859498327448. Ignoring."
obj.tagid = (ozoneBidRequest.params.placementId).toString();
obj.secure = window.location.protocol === 'https:' ? 1 : 0;
// is there a banner (or nothing declared, so banner is the default)?
Expand Down Expand Up @@ -141,6 +153,7 @@ export const spec = {
obj.ext = {'prebid': {'storedrequest': {'id': (ozoneBidRequest.params.placementId).toString()}}, 'ozone': {}};
obj.ext.ozone.adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu'
obj.ext.ozone.transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit
obj.ext.ozone.oz_pb_v = OZONEVERSION;
if (ozoneBidRequest.params.hasOwnProperty('customData')) {
obj.ext.ozone.customData = ozoneBidRequest.params.customData;
}
Expand All @@ -150,16 +163,14 @@ export const spec = {
if (ozoneBidRequest.params.hasOwnProperty('lotameData')) {
obj.ext.ozone.lotameData = ozoneBidRequest.params.lotameData;
}
if (ozoneBidRequest.hasOwnProperty('crumbs') && ozoneBidRequest.crumbs.hasOwnProperty('pubcid')) {
if (utils.deepAccess(ozoneBidRequest, 'crumbs.pubcid')) {
obj.ext.ozone.pubcid = ozoneBidRequest.crumbs.pubcid;
}
return obj;
});
utils.logInfo('tosendtags = ', tosendtags);

ozoneRequest.site = {'publisher': {'id': htmlParams.publisherId}, 'page': document.location.href};
ozoneRequest.test = parseInt(getTestQuerystringValue()); // will be 1 or 0
// utils.logInfo('_ozoneInternal is', _ozoneInternal);
// return the single request object OR the array:
if (singleRequest) {
utils.logInfo('OZONE: buildRequests starting to generate response for a single request');
Expand All @@ -177,7 +188,6 @@ export const spec = {
utils.logInfo('OZONE: buildRequests going to return for single: ', ret);
return ret;
}

// not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array.
let arrRet = tosendtags.map(imp => {
utils.logInfo('OZONE: buildRequests starting to generate non-single response, working on imp : ', imp);
Expand All @@ -201,67 +211,65 @@ export const spec = {
/**
* Interpret the response if the array contains BIDDER elements, in the format: [ [bidder1 bid 1, bidder1 bid 2], [bidder2 bid 1, bidder2 bid 2] ]
* NOte that in singleRequest mode this will be called once, else it will be called for each adSlot's response
*
* Updated April 2019 to return all bids, not just the one we decide is the 'winner'
*
* @param serverResponse
* @param request
* @returns {*}
*/
interpretResponse(serverResponse, request) {
utils.logInfo('OZONE: version' + OZONEVERSION + ' interpretResponse', serverResponse, request);
serverResponse = serverResponse.body || {};
if (serverResponse.seatbid) {
if (utils.isArray(serverResponse.seatbid)) {
// serverResponse seems good, let's get the list of bids from the request object:
let arrRequestBids = request.bidderRequest.bids;
// build up a list of winners, one for each bidId in arrBidIds
let arrWinners = [];
for (let i = 0; i < arrRequestBids.length; i++) {
let thisBid = arrRequestBids[i];
let ozoneInternalKey = thisBid.bidId;
let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid, serverResponse.seatbid);

if (winningBid == null) {
utils.logInfo('OZONE: FAILED to get winning bid for bid : ', thisBid, 'will skip. Possibly a non-single request, which will be missing some bid IDs');
continue;
}

const {defaultWidth, defaultHeight} = defaultSize(arrRequestBids[i]);
winningBid = ozoneAddStandardProperties(winningBid, defaultWidth, defaultHeight);

utils.logInfo('OZONE: Going to add the adserverTargeting custom parameters for key: ', ozoneInternalKey);
let adserverTargeting = {};
let allBidsForThisBidid = ozoneGetAllBidsForBidId(ozoneInternalKey, serverResponse.seatbid);
// add all the winning & non-winning bids for this bidId:
Object.keys(allBidsForThisBidid).forEach(function(bidderName, index, ar2) {
utils.logInfo('OZONE: inside allBidsForThisBidid:foreach', bidderName, index, ar2, allBidsForThisBidid);
adserverTargeting['oz_' + bidderName] = bidderName;
adserverTargeting['oz_' + bidderName + '_pb'] = String(allBidsForThisBidid[bidderName].price);
adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid);
adserverTargeting['oz_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain);
adserverTargeting['oz_' + bidderName + '_imp_id'] = String(allBidsForThisBidid[bidderName].impid);
});
// now add the winner data:
adserverTargeting['oz_auc_id'] = String(request.bidderRequest.auctionId);
adserverTargeting['oz_winner'] = String(winningSeat);
adserverTargeting['oz_winner_auc_id'] = String(winningBid.id);
adserverTargeting['oz_winner_imp_id'] = String(winningBid.impid);
adserverTargeting['oz_response_id'] = String(serverResponse.id);
if (!serverResponse.hasOwnProperty('seatbid')) { return []; }
if (typeof serverResponse.seatbid !== 'object') { return []; }
let arrAllBids = [];
serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute.
for (let i = 0; i < serverResponse.seatbid.length; i++) {
let sb = serverResponse.seatbid[i];
const {defaultWidth, defaultHeight} = defaultSize(request.bidderRequest.bids[i]);
for (let j = 0; j < sb.bid.length; j++) {
let thisBid = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight);

winningBid.adserverTargeting = adserverTargeting;
utils.logInfo('OZONE: winner is', winningBid);
arrWinners.push(winningBid);
utils.logInfo('OZONE: arrWinners is', arrWinners);
// from https://github.com/prebid/Prebid.js/pull/1082
if (utils.deepAccess(thisBid, 'ext.prebid.type') === VIDEO) {
utils.logInfo('OZONE: going to attach a renderer to:', j);
let renderConf = createObjectForInternalVideoRender(thisBid);
thisBid.renderer = Renderer.install(renderConf);
} else {
utils.logInfo('OZONE: bid is not a video, will not attach a renderer: ', j);
}
let winnersClean = arrWinners.filter(w => {
return (w.bidId); // will be cast to boolean

let ozoneInternalKey = thisBid.bidId;
let adserverTargeting = {};
// all keys for all bidders for this bid have to be added to all objects returned, else some keys will not be sent to ads?
let allBidsForThisBidid = ozoneGetAllBidsForBidId(ozoneInternalKey, serverResponse.seatbid);
// add all the winning & non-winning bids for this bidId:
utils.logInfo('OZONE: Going to iterate allBidsForThisBidId', allBidsForThisBidid);
Object.keys(allBidsForThisBidid).forEach(function(bidderName, index, ar2) {
adserverTargeting['oz_' + bidderName] = bidderName;
adserverTargeting['oz_' + bidderName + '_pb'] = String(allBidsForThisBidid[bidderName].price);
adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid);
adserverTargeting['oz_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain);
adserverTargeting['oz_' + bidderName + '_imp_id'] = String(allBidsForThisBidid[bidderName].impid);
adserverTargeting['oz_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId);
adserverTargeting['oz_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type);
if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) {
adserverTargeting['oz_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid);
}
});
utils.logInfo('OZONE: going to return winnersClean:', winnersClean);
return winnersClean;
} else {
return [];
// also add in the winning bid, to be sent to dfp
let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(ozoneInternalKey, serverResponse.seatbid);
adserverTargeting['oz_auc_id'] = String(request.bidderRequest.auctionId);
adserverTargeting['oz_winner'] = String(winningSeat);
adserverTargeting['oz_winner_auc_id'] = String(winningBid.id);
adserverTargeting['oz_winner_imp_id'] = String(winningBid.impid);
adserverTargeting['oz_response_id'] = String(serverResponse.id);
adserverTargeting['oz_pb_v'] = OZONEVERSION;
thisBid.adserverTargeting = adserverTargeting;
arrAllBids.push(thisBid);
}
} else {
return [];
}
return arrAllBids;
},
getUserSyncs(optionsType, serverResponse) {
if (!serverResponse || serverResponse.length === 0) {
Expand All @@ -275,6 +283,21 @@ export const spec = {
}
}
}
/**
* add a page-level-unique adId element to all server response bids.
* NOTE that this is distructive - it mutates the serverResponse object sent in as a parameter
* @param seatbid object (serverResponse.seatbid)
* @returns seatbid object
*/
export function injectAdIdsIntoAllBidResponses(seatbid) {
for (let i = 0; i < seatbid.length; i++) {
let sb = seatbid[i];
for (let j = 0; j < sb.bid.length; j++) {
sb.bid[j]['adId'] = sb.bid[j]['impid'] + '-' + i; // modify the bidId per-bid, so each bid has a unique adId within this response, and dfp can select one.
}
}
return seatbid;
}
export function checkDeepArray(Arr) {
if (Array.isArray(Arr)) {
if (Array.isArray(Arr[0])) {
Expand All @@ -300,15 +323,14 @@ export function defaultSize(thebidObj) {
* @param serverResponseSeatBid
* @returns {*} bid object
*/
export function ozoneGetWinnerForRequestBid(requestBid, serverResponseSeatBid) {
export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) {
let thisBidWinner = null;
let winningSeat = null;
for (let j = 0; j < serverResponseSeatBid.length; j++) {
let theseBids = serverResponseSeatBid[j].bid;
let thisSeat = serverResponseSeatBid[j].seat;
for (let k = 0; k < theseBids.length; k++) {
if (theseBids[k].impid === requestBid.bidId) { // we've found a matching server response bid for this request bid
// if (theseBids[k].impid === requestBid.adUnitCode) { // we've found a matching server response bid for this request bid
if (theseBids[k].impid === requestBidId) { // we've found a matching server response bid for this request bid
if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) {
thisBidWinner = theseBids[k];
winningSeat = thisSeat;
Expand All @@ -327,22 +349,88 @@ export function ozoneGetWinnerForRequestBid(requestBid, serverResponseSeatBid) {
* @returns {} = {ozone:{obj}, appnexus:{obj}, ... }
*/
export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) {
utils.logInfo('OZONE: ozoneGetAllBidsForBidId - starting, with: ', matchBidId, serverResponseSeatBid);
let objBids = {};
for (let j = 0; j < serverResponseSeatBid.length; j++) {
let theseBids = serverResponseSeatBid[j].bid;
let thisSeat = serverResponseSeatBid[j].seat;
for (let k = 0; k < theseBids.length; k++) {
if (theseBids[k].impid === matchBidId) { // we've found a matching server response bid for the request bid we're looking for
utils.logInfo('ozoneGetAllBidsForBidId - found matching bid: ', matchBidId, theseBids[k]);
objBids[thisSeat] = theseBids[k];
}
}
}
utils.logInfo('OZONE: ozoneGetAllBidsForBidId - going to return: ', objBids);
return objBids;
}

/**
* Round the bid price down according to the granularity
* @param price
* @param mediaType = video, banner or native
*/
export function getRoundedBid(price, mediaType) {
const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); // might be string or object or nothing; if set then this takes precedence over 'priceGranularity'
let objBuckets = config.getConfig('customPriceBucket'); // this is always an object - {} if strBuckets is not 'custom'
let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom'
let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets);
let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets);

utils.logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets);

let priceStringsObj = getPriceBucketString(
price,
theConfigObject,
config.getConfig('currency.granularityMultiplier')
);
utils.logInfo('priceStringsObj', priceStringsObj);
// by default, without any custom granularity set, you get granularity name : 'medium'
let granularityNamePriceStringsKeyMapping = {
'medium': 'med',
'custom': 'custom',
'high': 'high',
'low': 'low',
'dense': 'dense'
};
if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) {
let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey];
utils.logInfo('OZONE: looking for priceStringsKey:', priceStringsKey);
return priceStringsObj[priceStringsKey];
}
return priceStringsObj['auto'];
}

/**
* return the key to use to get the value out of the priceStrings object, taking into account anything at
* config.priceGranularity level or config.mediaType.xxx level
* I've noticed that the key specified by prebid core : config.getConfig('priceGranularity') does not properly
* take into account the 2-levels of config
*/
export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets) {
if (typeof mediaTypeGranularity === 'string') {
return mediaTypeGranularity;
}
if (typeof mediaTypeGranularity === 'object') {
return 'custom';
}
if (typeof strBuckets === 'string') {
return strBuckets;
}
return 'auto'; // fall back to a default key - should literally never be needed though.
}

/**
* return the object to use to create the custom value of the priceStrings object, taking into account anything at
* config.priceGranularity level or config.mediaType.xxx level
*/
export function getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets) {
if (typeof mediaTypeGranularity === 'object') {
return mediaTypeGranularity;
}
if (strBuckets === 'custom') {
return objBuckets;
}
return '';
}

/**
* We expect to be able to find a standard set of properties on winning bid objects; add them here.
* @param seatBid
Expand Down Expand Up @@ -379,5 +467,37 @@ export function getTestQuerystringValue() {
return 0;
}

/**
* Generate a random number per ad; I'll use the current ms timestamp, then append 8 random alpha/numeric characters
* Randomness : 1 in 208 billion random combinations per-millisecond, non-repeating sequence.
*
* @returns {*}
*/
export function pgGuid() {
return new Date().getTime() + 'xxxxxxxx'.replace(/x/g, function(c) {
return Math.round((Math.random() * 36)).toString(36);
});
}

function createObjectForInternalVideoRender(bid) {
let obj = {
url: OZONE_RENDERER_URL,
callback: () => onOutstreamRendererLoaded(bid)
}
return obj;
}

function onOutstreamRendererLoaded(bid) {
try {
bid.renderer.setRender(outstreamRender);
} catch (err) {
utils.logWarn('Prebid Error calling setRender on renderer', err)
}
}

function outstreamRender(bid) {
window.ozoneVideo.outstreamRender(bid);
}

registerBidder(spec);
utils.logInfo('OZONE: ozoneBidAdapter ended');
Loading