modules/silverpushBidAdapter.js
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import * as utils from '../src/utils.js';
import { mergeDeep } from '../src/utils.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
import { Renderer } from '../src/Renderer.js';
import { ajax } from '../src/ajax.js';
const BIDDER_CODE = 'silverpush';
const bidderConfig = 'sp_pb_ortb';
const bidderVersion = '1.0.0';
const DEFAULT_CURRENCY = 'USD';
export const REQUEST_URL = 'https://prebid.chocolateplatform.co/bidder/?identifier=prebidchoc';
export const SP_OUTSTREAM_PLAYER_URL = 'https://xaido.sgp1.cdn.digitaloceanspaces.com/prebid/spoutstream.min.js';
const VIDEO_ORTB_PARAMS = [
'mimes',
'minduration',
'maxduration',
'placement',
'protocols',
'startdelay',
'skip',
'skipafter',
'minbitrate',
'maxbitrate',
'delivery',
'playbackmethod',
'api',
'linearity'
];
export const VIDEO_ORTB_REQUIRED = ['api', 'mimes', 'placement', 'protocols', 'minduration', 'maxduration', 'startdelay'];
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid,
buildRequests,
interpretResponse,
onBidWon,
getRequest: function(endpoint) {
ajax(endpoint, null, undefined, {method: 'GET'});
},
getOS: function(ua) {
if (ua.indexOf('Windows') != -1) { return 'Windows'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'iOS'; } else if (ua.indexOf('Mac OS X') != -1) { return 'macOS'; } else if (ua.match(/Android/)) { return 'Android'; } else if (ua.indexOf('Linux') != -1) { return 'Linux'; } else { return 'Unknown'; }
}
};
registerBidder(spec);
export const CONVERTER = ortbConverter({
context: {
netRevenue: true,
ttl: 300
},
imp(buildImp, bidRequest, context) {
let imp = buildImp(bidRequest, context);
if (bidRequest.mediaTypes[VIDEO]) {
imp = buildVideoImp(bidRequest, imp);
} else if (bidRequest.mediaTypes[BANNER]) {
imp = buildBannerImp(bidRequest, imp);
}
const bidFloor = getBidFloor(bidRequest);
utils.deepSetValue(imp, 'bidfloor', bidFloor);
if (bidRequest.params.deals && bidRequest.params.deals.length > 0) {
utils.deepSetValue(imp, 'pmp', { deals: bidRequest.params.deals });
}
return imp;
},
request(buildRequest, imps, bidderRequest, context) {
const req = buildRequest(imps, bidderRequest, context);
mergeDeep(req, {
at: 1,
ext: {
bc: `${bidderConfig}_${bidderVersion}`
}
})
let userAgent = navigator.userAgent;
utils.deepSetValue(req, 'device.os', spec.getOS(userAgent));
utils.deepSetValue(req, 'device.devicetype', _isMobile() ? 1 : _isConnectedTV() ? 3 : 2);
const bid = context.bidRequests[0];
if (bid.params.publisherId) {
utils.deepSetValue(req, 'site.publisher.id', bid.params.publisherId);
}
return req;
},
bidResponse(buildBidResponse, bid, context) {
let bidResponse = buildBidResponse(bid, context);
if (bid.ext) {
bidResponse.meta.networkId = bid.ext.dsp_id;
bidResponse.meta.advertiserId = bid.ext.buyer_id;
bidResponse.meta.brandId = bid.ext.brand_id;
}
if (context.ortbResponse.ext && context.ortbResponse.ext.paf) {
bidResponse.meta.paf = Object.assign({}, context.ortbResponse.ext.paf);
bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id');
}
bidResponse = buildVideoVastResponse(bidResponse);
bidResponse = buildVideoOutstreamResponse(bidResponse, context)
return bidResponse;
},
response(buildResponse, bidResponses, ortbResponse, context) {
const response = buildResponse(bidResponses, ortbResponse, context);
let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs');
if (fledgeAuctionConfigs) {
fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => {
return Object.assign({
bidId,
auctionSignals: {}
}, cfg);
});
return {
bids: response.bids,
fledgeAuctionConfigs,
}
} else {
return response.bids
}
}
});
function isBidRequestValid(bidRequest) {
return (isPublisherIdValid(bidRequest) && (isValidBannerRequest(bidRequest) || isValidVideoRequest(bidRequest)));
}
function isPublisherIdValid(bidRequest) {
let pubId = utils.deepAccess(bidRequest, 'params.publisherId');
return (pubId != null && utils.isStr(pubId) && pubId != '');
}
function isValidBannerRequest(bidRequest) {
const bannerSizes = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}.sizes`);
return utils.isArray(bannerSizes) && bannerSizes.length > 0 && bannerSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1]));
}
function isValidVideoRequest(bidRequest) {
const videoSizes = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.playerSize`);
const PARAM_EXISTS = VIDEO_ORTB_REQUIRED.every(param => utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.${param}`) != null);
return PARAM_EXISTS && utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1]));
}
function buildRequests(validBids, bidderRequest) {
let videoBids = validBids.filter(bid => isVideoBid(bid));
let bannerBids = validBids.filter(bid => isBannerBid(bid));
let requests = [];
bannerBids.forEach(bid => {
requests.push(createRequest([bid], bidderRequest, BANNER));
});
videoBids.forEach(bid => {
requests.push(createRequest([bid], bidderRequest, VIDEO));
});
return requests;
}
function buildVideoImp(bidRequest, imp) {
if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') {
imp.video.placement = imp.video.placement || 4;
}
const videoMediaType = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`);
const videoSizes = (videoMediaType && videoMediaType.playerSize) || [];
if (videoSizes && videoSizes.length > 0) {
utils.deepSetValue(imp, 'video.w', videoSizes[0][0]);
utils.deepSetValue(imp, 'video.h', videoSizes[0][1]);
}
const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {});
const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {});
const videoParams = { ...videoAdUnitParams, ...videoBidderParams };
VIDEO_ORTB_PARAMS.forEach((param) => {
if (videoParams.hasOwnProperty(param)) {
utils.deepSetValue(imp, `video.${param}`, videoParams[param]);
}
});
return { ...imp };
}
function buildBannerImp(bidRequest, imp) {
const bannerSizes = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}.sizes`, []);
if (bannerSizes && bannerSizes.length > 0) {
utils.deepSetValue(imp, 'banner.w', bannerSizes[0][0]);
utils.deepSetValue(imp, 'banner.h', bannerSizes[0][1]);
}
return {...imp};
}
function createRequest(bidRequests, bidderRequest, mediaType) {
return {
method: 'POST',
url: REQUEST_URL,
data: CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } })
}
}
function buildVideoVastResponse(bidResponse) {
if (bidResponse.mediaType == VIDEO && bidResponse.vastXml) {
bidResponse.vastUrl = bidResponse.vastXml;
}
return { ...bidResponse }
}
function buildVideoOutstreamResponse(bidResponse, context) {
if (context.bidRequest.mediaTypes[VIDEO]?.context === 'outstream') {
bidResponse.rendererUrl = SP_OUTSTREAM_PLAYER_URL;
bidResponse.adUnitCode = context.bidRequest.adUnitCode;
bidResponse.renderer = Renderer.install({
id: bidResponse.requestId,
adUnitCode: context.bidRequest.adUnitCode,
url: bidResponse.rendererUrl
});
bidResponse.renderer.setRender(_renderer(bidResponse));
bidResponse.renderer.render(bidResponse);
}
return {...bidResponse};
}
function getBidFloor(bid) {
const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY;
if (typeof bid.getFloor !== 'function') {
return utils.deepAccess(bid, 'params.bidFloor', 0.05);
}
const bidFloor = bid.getFloor({
currency: currency,
mediaType: '*',
size: '*',
});
return bidFloor.floor;
}
function _renderer(bid) {
bid.renderer.push(() => {
if (typeof window.SPOutStreamPlayer === 'function') {
const spoplayer = new window.SPOutStreamPlayer(bid);
spoplayer.on('ready', () => {
spoplayer.startAd();
});
try {
let vastUrlbt = 'data:text/xml;charset=utf-8;base64,' + btoa(bid.vastUrl.replace(/\\"/g, '"'));
spoplayer.load(vastUrlbt).then(function() {
window.spoplayer = spoplayer;
}).catch(function(reason) {
setTimeout(function() { throw reason; }, 0);
});
} catch (err) {
utils.logMessage(err);
}
} else {
utils.logMessage(`Silverpush outstream player is not defined`);
}
});
}
function isVideoBid(bid) {
return utils.deepAccess(bid, 'mediaTypes.video');
}
function isBannerBid(bid) {
return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid);
}
function interpretResponse(resp, req) {
if (!resp.body) {
resp.body = { nbr: 0 };
}
return CONVERTER.fromORTB({ request: req.data, response: resp.body });
}
function onBidWon(bid) {
if (bid == null) { return; }
if (bid['burl'] == null) { return; }
let burlMac = bid['burl'];
burlMac = burlMac.replace('$' + '{AUCTION_PRICE}', bid['cpm']);
burlMac = burlMac.replace('$' + '{AUCTION_ID}', bid['auctionId']);
burlMac = burlMac.replace('$' + '{AUCTION_IMP_ID}', bid['requestId']);
burlMac = burlMac.replace('$' + '{AUCTION_AD_ID}', bid['adId']);
burlMac = burlMac.replace('$' + '{AUCTION_SEAT_ID}', bid['seatBidId']);
spec.getRequest(burlMac);
}
function _isMobile() {
return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent);
}
function _isConnectedTV() {
return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent);
}