Skip to content

Commit

Permalink
YieldNexus Bid Adapter v1 (#2855)
Browse files Browse the repository at this point in the history
* YieldNexus Bid Adapter v1

* Apply code-review changes requested by @jsneker

Removed redundant decration of 'onTik which does nothing.
Import banner & video constants instead of hard-coding them.
Remove usage of "config.getConfig('pageurl')".

Also updated the "spid" to 1249.

* Remove unused 'config' import

* Update demo account IDs for both banner & video

* Add video renderer support

* Fix unit test checking url

* Fix code linting errors

* Add missing import statement
  • Loading branch information
ericyld authored and jsnellbaker committed Sep 4, 2018
1 parent 890315b commit edc9d43
Show file tree
Hide file tree
Showing 3 changed files with 556 additions and 0 deletions.
193 changes: 193 additions & 0 deletions modules/yieldNexusBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
import { BANNER, VIDEO } from 'src/mediaTypes';
import { Renderer } from 'src/Renderer';

const pixKey = 'utrk';

function startsWith(str, search) {
return str.substr(0, search.length) === search;
}

export const spec = {
code: 'yieldnexus',
aliases: [],
supportedMediaTypes: [BANNER, VIDEO],

isBidRequestValid: function(bid) {
if (!bid.params.spid) {
return false;
} else if (typeof bid.params.spid !== 'string') {
return false;
}
return (typeof bid.params.instl === 'undefined' || bid.params.instl === 0 || bid.params.instl === 1) &&
(typeof bid.params.bidfloor === 'undefined' || typeof bid.params.bidfloor === 'number') &&
(typeof bid.params['protocols'] === 'undefined' || Array.isArray(bid.params['protocols'])) &&
(typeof bid.params['adpos'] === 'undefined' || typeof bid.params['adpos'] === 'number');
},

buildRequests: function(validBidRequests, bidderRequest) {
return validBidRequests.map(bidRequest => {
let referrer = '';
try {
referrer = window.top.document.referrer;
} catch (e) {
try {
referrer = window.document.referrer;
} catch (e) {
}
}
const url = utils.getTopWindowUrl();
const domainStart = url.indexOf('://') + 3;
const req = {
id: bidRequest.auctionId,
site: {
domain: url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)),
page: url,
ref: referrer
},
device: {
ua: navigator.userAgent
},
imp: [],
ext: {}
};
if (bidderRequest && bidderRequest.gdprConsent) {
req.ext.gdpr_consent = {
consent_string: bidderRequest.gdprConsent.consentString,
consent_required: bidderRequest.gdprConsent.gdprApplies
};
}
let topFrame;
try {
topFrame = window.top === window ? 1 : 0;
} catch (e) {
topFrame = 0;
}
const imp = {
id: bidRequest.transactionId,
instl: bidRequest.params.instl === 1 ? 1 : 0,
tagid: bidRequest.adUnitCode,
bidfloor: bidRequest.params.bidfloor || 0,
bidfloorcur: 'USD',
secure: startsWith(utils.getTopWindowUrl().toLowerCase(), 'http://') ? 0 : 1
};
if (!bidRequest.mediaTypes || bidRequest.mediaTypes.banner) {
imp.banner = {
w: bidRequest.sizes.length ? bidRequest.sizes[0][0] : 300,
h: bidRequest.sizes.length ? bidRequest.sizes[0][1] : 250,
pos: bidRequest.params.pos || 0,
topframe: topFrame
};
} else if (bidRequest.mediaTypes.video) {
imp.video = {
w: bidRequest.sizes.length ? bidRequest.sizes[0][0] : 300,
h: bidRequest.sizes.length ? bidRequest.sizes[0][1] : 250,
protocols: bidRequest.params.protocols || [1, 2, 3, 4, 5, 6],
pos: bidRequest.params.pos || 0,
topframe: topFrame
};
} else {
return;
}
req.imp.push(imp);
return {
method: 'POST',
url: `https://ssp.ynxs.io/r/${bidRequest.params.spid}/bidr?bidder=prebid&rformat=open_rtb&reqformat=rtb_json` + (bidRequest.params.query ? '&' + bidRequest.params.query : ''),
data: req,
bidRequest
};
});
},

interpretResponse: function(serverResponse, bidRequest) {
const outBids = [];
if (serverResponse && serverResponse.body) {
const bids = serverResponse.body.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []);
bids.forEach(bid => {
const outBid = {
requestId: bidRequest.bidRequest.bidId,
cpm: bid.price,
width: bid.w,
height: bid.h,
ttl: 15 * 60,
creativeId: bid.crid,
netRevenue: true,
currency: bid.cur || serverResponse.body.cur
};
if (!bidRequest.bidRequest.mediaTypes || bidRequest.bidRequest.mediaTypes.banner) {
outBids.push(Object.assign({}, outBid, { mediaType: 'banner', ad: bid.adm }));
} else if (bidRequest.bidRequest.mediaTypes.video) {
const context = utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.video.context');
outBids.push(Object.assign({}, outBid, {
mediaType: 'video',
vastUrl: bid.ext.vast_url,
vastXml: bid.adm,
renderer: context === 'outstream' ? newRenderer(bidRequest.bidRequest, bid) : undefined
}));
}
});
}
return outBids;
},

getUserSyncs: function(syncOptions, serverResponses, gdprConsent) {
const syncs = [];
const gdprApplies = gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean') ? gdprConsent.gdprApplies : false;
const suffix = gdprApplies ? 'gc=' + encodeURIComponent(gdprConsent.consentString) : 'gc=missing';
serverResponses.forEach(resp => {
if (resp.body) {
const bidResponse = resp.body;
if (bidResponse.ext && Array.isArray(bidResponse.ext[pixKey])) {
bidResponse.ext[pixKey].forEach(pixel => syncs.push({ type: pixel.type, url: pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix) }));
}
if (Array.isArray(bidResponse.seatbid)) {
bidResponse.seatbid.forEach(seatBid => {
if (Array.isArray(seatBid.bid)) {
seatBid.bid.forEach(bid => {
if (bid.ext && Array.isArray(bid.ext[pixKey])) {
bid.ext[pixKey].forEach(pixel => syncs.push({ type: pixel.type, url: pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix) }));
}
});
}
});
}
}
});
return syncs;
}
};

function newRenderer(bidRequest, bid, rendererOptions = {}) {
let rendererUrl = '//s.gambid.io/video/latest/renderer.js';
if (bid.ext && bid.ext.renderer_url) {
rendererUrl = bid.ext.renderer_url;
}
if (bidRequest.params && bidRequest.params.rendererUrl) {
rendererUrl = bidRequest.params.rendererUrl;
}
const renderer = Renderer.install({ url: rendererUrl, config: rendererOptions, loaded: false });
renderer.setRender(renderOutstream);
return renderer;
}

function renderOutstream(bid) {
bid.renderer.push(() => {
window[ 'GambidPlayer' ].renderAd({
id: bid.adUnitCode + '/' + bid.adId,
debug: window.location.href.indexOf('pbjsDebug') >= 0,
placement: document.getElementById(bid.adUnitCode),
width: bid.width,
height: bid.height,
events: {
ALL_ADS_COMPLETED: () => window.setTimeout(() => {
window[ 'GambidPlayer' ].removeAd(bid.adUnitCode + '/' + bid.adId);
}, 300)
},
vastUrl: bid.vastUrl,
vastXml: bid.vastXml
});
});
}

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

```
Module Name: YieldNexus Bid Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

# Description

Adds support to query the YieldNexus platform for bids. The YieldNexus platform supports banners & video.

Only one parameter is required: `spid`, which provides your YieldNexus account number.

# Test Parameters
```
var adUnits = [
// Banner:
{
code: 'banner-ad-unit',
sizes: [[300, 250]],
bids: [{
bidder: 'yieldnexus',
params: {
spid: '1253', // your supply ID in your YieldNexus dashboard
bidfloor: 0.03, // an optional custom bid floor
adpos: 1, // ad position on the page (optional)
instl: 0 // interstitial placement? (0 or 1, optional)
}
}]
},
// Outstream video:
{
code: 'video-ad-unit',
sizes: [[640, 480]],
mediaTypes: {
video: {
context: 'outstream',
playerSize: [640, 480]
}
},
bids: [ {
bidder: 'yieldnexus',
params: {
spid: '1254', // your supply ID in your YieldNexus dashboard
bidfloor: 0.03, // an optional custom bid floor
adpos: 1, // ad position on the page (optional)
instl: 0 // interstitial placement? (0 or 1, optional)
}
}]
}
];
```
Loading

0 comments on commit edc9d43

Please sign in to comment.