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

fl-1241 - integrated video bid support into rubicon adapter. Updated … #7

Closed
wants to merge 7 commits into from
3 changes: 2 additions & 1 deletion adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
},
{
"rubicon": {
"alias": "rubiconLite"
"alias": "rubiconLite",
"supportedMediaTypes": ["video"]
}
},
{
Expand Down
132 changes: 117 additions & 15 deletions src/adapters/rubicon.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import { ajax } from 'src/ajax';
import { STATUS } from 'src/constants';

const RUBICON_BIDDER_CODE = 'rubicon';
const INTEGRATION = 'pbjs.lite';

// 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',
Expand Down Expand Up @@ -49,7 +56,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 +89,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: INTEGRATION,
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;
}
Copy link
Author

Choose a reason for hiding this comment

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

I might split out the validation of the response by type that way we can still do these checks for non-video and then video can have a different set of response validations.

Choose a reason for hiding this comment

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

I believe these checks are identical for both display and video. Only difference was grabbing the video ads response correctly from frankenstein which comes back as an objet and not an array directly because it implements SRA. If further individual validation becomes necessary in the future, we can break it out based on mediaType.


postData.slots.push(slotData);

return(JSON.stringify(postData));
}

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

Expand Down Expand Up @@ -113,8 +201,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', INTEGRATION,
'p_screen_res', _getScreenResolution(),
'kw', keywords,
'tk_user_key', userId
];
Expand All @@ -136,7 +224,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 +239,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 +272,17 @@ 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.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
Loading