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

Rubicon video support #950

Merged
3 changes: 2 additions & 1 deletion adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
},
{
"rubicon": {
"alias": "rubiconLite"
"alias": "rubiconLite",
"supportedMediaTypes": ["video"]
}
},
{
Expand Down
137 changes: 122 additions & 15 deletions src/adapters/rubicon.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import { STATUS } from 'src/constants';

const RUBICON_BIDDER_CODE = 'rubicon';

// use deferred function call since version isn't defined yet at this point
function getIntegration() {
return 'pbjs_lite_' + $$PREBID_GLOBAL$$.version;
}

// use protocol relative urls for http or https
const FASTLANE_ENDPOINT = '//fastlane.rubiconproject.com/a/api/fastlane.json';
const VIDEO_ENDPOINT = '//optimized-by-adv.rubiconproject.com/v1/auction/video';

const TIMEOUT_BUFFER = 500;

var sizeMap = {
1:'468x60',
2:'728x90',
Expand Down Expand Up @@ -49,7 +60,12 @@ function RubiconAdapter() {

bids.forEach(bid => {
try {
ajax(buildOptimizedCall(bid), bidCallback, undefined, {withCredentials: true});
// Video endpoint only accepts POST calls
if (bid.mediaType === 'video') {
ajax(VIDEO_ENDPOINT, bidCallback, buildVideoRequestPayload(bid, bidderRequest), {withCredentials: true});
} else {
ajax(buildOptimizedCall(bid), bidCallback, undefined, {withCredentials: true});
}
} catch(err) {
utils.logError('Error sending rubicon request for placement code ' + bid.placementCode, null, err);
addErrorBid();
Expand Down Expand Up @@ -77,6 +93,82 @@ function RubiconAdapter() {
});
}

function _getScreenResolution() {
return [window.screen.width, window.screen.height].join('x');
}

function buildVideoRequestPayload(bid, bidderRequest) {
bid.startTime = new Date().getTime();

let params = bid.params;

if(!params || typeof params.video !== 'object') {
throw 'Invalid Video Bid';
}

let size;
if(params.video.playerWidth && params.video.playerHeight) {
size = [
params.video.playerWidth,
params.video.playerHeight
];
} else if(
Array.isArray(bid.sizes) && bid.sizes.length > 0 &&
Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1
) {
size = bid.sizes[0];
} else {
throw "Invalid Video Bid - No size provided";
}

let postData = {
page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer,
resolution: _getScreenResolution(),
account_id: params.accountId,
integration: getIntegration(),
timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart + TIMEOUT_BUFFER),
stash_creatives: true,
ae_pass_through_parameters: params.video.aeParams,
slots: []
};

// Define the slot object
let slotData = {
site_id: params.siteId,
zone_id: params.zoneId,
position: params.position || 'btf',
floor: 0.01,
element_id: bid.placementCode,
name: bid.placementCode,
language: params.video.language,
width: size[0],
height: size[1]
};

// check and add inventory, keywords, visitor and size_id data
if(params.video.size_id) {
slotData.size_id = params.video.size_id;
} else {
throw "Invalid Video Bid - Invalid Ad Type!";
}

if(params.inventory && typeof params.inventory === 'object') {
slotData.inventory = params.inventory;
}

if(params.keywords && Array.isArray(params.keywords)) {
slotData.keywords = params.keywords;
}

if(params.visitor && typeof params.visitor === 'object') {
slotData.visitor = params.visitor;
}

postData.slots.push(slotData);

return(JSON.stringify(postData));
}

function buildOptimizedCall(bid) {
bid.startTime = new Date().getTime();

Expand Down Expand Up @@ -113,8 +205,8 @@ function RubiconAdapter() {
'alt_size_ids', parsedSizes.slice(1).join(',') || undefined,
'p_pos', position,
'rp_floor', '0.01',
'tk_flint', 'pbjs.lite',
'p_screen_res', window.screen.width +'x'+ window.screen.height,
'tk_flint', getIntegration(),
'p_screen_res', _getScreenResolution(),
'kw', keywords,
'tk_user_key', userId
];
Expand All @@ -136,7 +228,7 @@ function RubiconAdapter() {
(memo, curr, index) =>
index % 2 === 0 && queryString[index + 1] !== undefined ?
memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' : memo,
'//fastlane.rubiconproject.com/a/api/fastlane.json?' // use protocol relative link for http or https
FASTLANE_ENDPOINT + '?'
).slice(0, -1); // remove trailing &
}

Expand All @@ -151,23 +243,29 @@ function RubiconAdapter() {
</html>`;

function handleRpCB(responseText, bidRequest) {
let responseObj = JSON.parse(responseText); // can throw
var responseObj = JSON.parse(responseText), // can throw
ads = responseObj.ads,
adResponseKey = bidRequest.placementCode;

if(
typeof responseObj !== 'object' ||
responseObj.status !== 'ok' ||
!Array.isArray(responseObj.ads) ||
responseObj.ads.length < 1
) {
// check overall response
if(typeof responseObj !== 'object' || responseObj.status !== 'ok') {
throw 'bad response';
}

var ads = responseObj.ads;
// video ads array is wrapped in an object
if (bidRequest.mediaType === 'video' && typeof ads === 'object') {
ads = ads[adResponseKey];
}

// check the ad response
if(!Array.isArray(ads) || ads.length < 1) {
throw 'invalid ad response';
}

// if there are multiple ads, sort by CPM
ads = ads.sort(_adCpmSort);

ads.forEach(function (ad) {
ads.forEach(ad => {
if(ad.status !== 'ok') {
throw 'bad ad status';
}
Expand All @@ -178,9 +276,18 @@ function RubiconAdapter() {
bid.creative_id = ad.ad_id;
bid.bidderCode = bidRequest.bidder;
bid.cpm = ad.cpm || 0;
bid.ad = _renderCreative(ad.script, ad.impression_id);
[bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num));
bid.dealId = ad.deal;
if (bidRequest.mediaType === 'video') {
bid.width = bidRequest.params.video.playerWidth;
bid.height = bidRequest.params.video.playerHeight;
bid.vastUrl = ad.creative_depot_url;
bid.descriptionUrl = ad.impression_id;
bid.impression_id = ad.impression_id;
} else {
bid.ad = _renderCreative(ad.script, ad.impression_id);
[bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num));
}


try {
bidmanager.addBidResponse(bidRequest.placementCode, bid);
Expand Down
2 changes: 1 addition & 1 deletion src/adserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ exports.dfpAdserver = function (options, urlComponents) {
var bid = adserver.getWinningBidByCode();
this.urlComponents.search.description_url = encodeURIComponent(bid.descriptionUrl);
this.urlComponents.search.cust_params = getCustomParams(bid.adserverTargeting);
this.urlComponents.correlator = Date.now();
this.urlComponents.search.correlator = Date.now();
};

adserver.verifyAdserverTag = function() {
Expand Down
Loading