mk9-prebid
Version:
Header Bidding Management Library
462 lines (405 loc) • 14.2 kB
JavaScript
import * as utils from '../src/utils.js';
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { Renderer } from '../src/Renderer.js';
import { VIDEO, BANNER } from '../src/mediaTypes.js';
import find from 'core-js-pure/features/array/find.js';
import includes from 'core-js-pure/features/array/includes.js';
const ADAPTER_VERSION = '1.17';
const ADAPTER_NAME = 'BFIO_PREBID';
const OUTSTREAM = 'outstream';
const CURRENCY = 'USD';
export const VIDEO_ENDPOINT = 'https://reachms.bfmio.com/bid.json?exchange_id=';
export const BANNER_ENDPOINT = 'https://display.bfmio.com/prebid_display';
export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js';
export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter'];
export const DEFAULT_MIMES = ['video/mp4', 'application/javascript'];
export const SUPPORTED_USER_IDS = [
{ key: 'tdid', source: 'adserver.org', rtiPartner: 'TDID', queryParam: 'tdid' },
{ key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' },
{ key: 'uid2.id', source: 'uidapi.com', rtiPartner: 'UID2', queryParam: 'uid2' },
{ key: 'haloId', source: 'audigent.com', atype: 1, queryParam: 'haloid' }
];
let appId = '';
export const spec = {
code: 'beachfront',
supportedMediaTypes: [ VIDEO, BANNER ],
isBidRequestValid(bid) {
return !!(isVideoBidValid(bid) || isBannerBidValid(bid));
},
buildRequests(bids, bidderRequest) {
let requests = [];
let videoBids = bids.filter(bid => isVideoBidValid(bid));
let bannerBids = bids.filter(bid => isBannerBidValid(bid));
videoBids.forEach(bid => {
appId = getVideoBidParam(bid, 'appId');
requests.push({
method: 'POST',
url: VIDEO_ENDPOINT + appId,
data: createVideoRequestData(bid, bidderRequest),
bidRequest: bid
});
});
if (bannerBids.length) {
appId = getBannerBidParam(bannerBids[0], 'appId');
requests.push({
method: 'POST',
url: BANNER_ENDPOINT,
data: createBannerRequestData(bannerBids, bidderRequest),
bidRequest: bannerBids
});
}
return requests;
},
interpretResponse(response, { bidRequest }) {
response = response.body;
if (isVideoBid(bidRequest)) {
if (!response || !response.bidPrice) {
utils.logWarn(`No valid video bids from ${spec.code} bidder`);
return [];
}
let sizes = getVideoSizes(bidRequest);
let firstSize = getFirstSize(sizes);
let context = utils.deepAccess(bidRequest, 'mediaTypes.video.context');
let responseType = getVideoBidParam(bidRequest, 'responseType') || 'both';
let responseMeta = Object.assign({ mediaType: VIDEO, advertiserDomains: [] }, response.meta);
let bidResponse = {
requestId: bidRequest.bidId,
bidderCode: spec.code,
cpm: response.bidPrice,
width: firstSize.w,
height: firstSize.h,
creativeId: response.crid || response.cmpId,
meta: responseMeta,
renderer: context === OUTSTREAM ? createRenderer(bidRequest) : null,
mediaType: VIDEO,
currency: CURRENCY,
netRevenue: true,
ttl: 300
};
if (responseType === 'nurl' || responseType === 'both') {
bidResponse.vastUrl = response.url;
}
if (responseType === 'adm' || responseType === 'both') {
bidResponse.vastXml = response.vast;
}
return bidResponse;
} else {
if (!response || !response.length) {
utils.logWarn(`No valid banner bids from ${spec.code} bidder`);
return [];
}
return response
.filter(bid => bid.adm)
.map((bid) => {
let request = find(bidRequest, req => req.adUnitCode === bid.slot);
let responseMeta = Object.assign({ mediaType: BANNER, advertiserDomains: [] }, bid.meta);
return {
requestId: request.bidId,
bidderCode: spec.code,
ad: bid.adm,
creativeId: bid.crid,
cpm: bid.price,
width: bid.w,
height: bid.h,
meta: responseMeta,
mediaType: BANNER,
currency: CURRENCY,
netRevenue: true,
ttl: 300
};
});
}
},
getUserSyncs(syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '') {
let syncs = [];
let { gdprApplies, consentString = '' } = gdprConsent;
let bannerResponse = find(serverResponses, (res) => utils.isArray(res.body));
if (bannerResponse) {
if (syncOptions.iframeEnabled) {
bannerResponse.body
.filter(bid => bid.sync)
.forEach(bid => {
syncs.push({
type: 'iframe',
url: bid.sync
});
});
}
} else if (syncOptions.iframeEnabled) {
syncs.push({
type: 'iframe',
url: `https://sync.bfmio.com/sync_iframe?ifg=1&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString}&gce=1&us_privacy=${uspConsent}`
});
} else if (syncOptions.pixelEnabled) {
syncs.push({
type: 'image',
url: `https://sync.bfmio.com/syncb?pid=144&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString}&gce=1&us_privacy=${uspConsent}`
});
}
return syncs;
}
};
function createRenderer(bidRequest) {
const renderer = Renderer.install({
id: bidRequest.bidId,
url: OUTSTREAM_SRC,
loaded: false
});
renderer.setRender(bid => {
bid.renderer.push(() => {
window.Beachfront.Player(bid.adUnitCode, {
adTagUrl: bid.vastUrl,
width: bid.width,
height: bid.height,
expandInView: getPlayerBidParam(bidRequest, 'expandInView', false),
collapseOnComplete: getPlayerBidParam(bidRequest, 'collapseOnComplete', true),
progressColor: getPlayerBidParam(bidRequest, 'progressColor'),
adPosterColor: getPlayerBidParam(bidRequest, 'adPosterColor')
});
});
});
return renderer;
}
function getFirstSize(sizes) {
return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined };
}
function parseSizes(sizes) {
return utils.parseSizesInput(sizes).map(size => {
let [ width, height ] = size.split('x');
return {
w: parseInt(width, 10) || undefined,
h: parseInt(height, 10) || undefined
};
});
}
function getVideoSizes(bid) {
return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes);
}
function getBannerSizes(bid) {
return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes);
}
function getOsVersion() {
let clientStrings = [
{ s: 'Android', r: /Android/ },
{ s: 'iOS', r: /(iPhone|iPad|iPod)/ },
{ s: 'Mac OS X', r: /Mac OS X/ },
{ s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
{ s: 'Linux', r: /(Linux|X11)/ },
{ s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
{ s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
{ s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
{ s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
{ s: 'Windows Vista', r: /Windows NT 6.0/ },
{ s: 'Windows Server 2003', r: /Windows NT 5.2/ },
{ s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
{ s: 'UNIX', r: /UNIX/ },
{ s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ }
];
let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent));
return cs ? cs.s : 'unknown';
}
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);
}
function getDoNotTrack() {
return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes';
}
function isVideoBid(bid) {
return utils.deepAccess(bid, 'mediaTypes.video');
}
function isBannerBid(bid) {
return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid);
}
function getVideoBidParam(bid, key) {
return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key);
}
function getBannerBidParam(bid, key) {
return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key);
}
function getPlayerBidParam(bid, key, defaultValue) {
let param = utils.deepAccess(bid, 'params.player.' + key);
return param === undefined ? defaultValue : param;
}
function getBannerBidFloor(bid) {
let floorInfo = utils.isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'banner', size: '*' }) : {};
return floorInfo.floor || getBannerBidParam(bid, 'bidfloor');
}
function getVideoBidFloor(bid) {
let floorInfo = utils.isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'video', size: '*' }) : {};
return floorInfo.floor || getVideoBidParam(bid, 'bidfloor');
}
function isVideoBidValid(bid) {
return isVideoBid(bid) && getVideoBidParam(bid, 'appId') && getVideoBidParam(bid, 'bidfloor');
}
function isBannerBidValid(bid) {
return isBannerBid(bid) && getBannerBidParam(bid, 'appId') && getBannerBidParam(bid, 'bidfloor');
}
function getTopWindowLocation(bidderRequest) {
let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer;
return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true });
}
function getTopWindowReferrer() {
try {
return window.top.document.referrer;
} catch (e) {
return '';
}
}
function getEids(bid) {
return SUPPORTED_USER_IDS
.map(getUserId(bid))
.filter(x => x);
}
function getUserId(bid) {
return ({ key, source, rtiPartner, atype }) => {
let id = utils.deepAccess(bid, `userId.${key}`);
return id ? formatEid(id, source, rtiPartner, atype) : null;
};
}
function formatEid(id, source, rtiPartner, atype) {
let uid = { id };
if (rtiPartner) {
uid.ext = { rtiPartner };
}
if (atype) {
uid.atype = atype;
}
return {
source,
uids: [uid]
};
}
function getVideoTargetingParams(bid) {
const result = {};
const excludeProps = ['playerSize', 'context', 'w', 'h'];
Object.keys(Object(bid.mediaTypes.video))
.filter(key => !includes(excludeProps, key))
.forEach(key => {
result[ key ] = bid.mediaTypes.video[ key ];
});
Object.keys(Object(bid.params.video))
.filter(key => includes(VIDEO_TARGETING, key))
.forEach(key => {
result[ key ] = bid.params.video[ key ];
});
return result;
}
function createVideoRequestData(bid, bidderRequest) {
let sizes = getVideoSizes(bid);
let firstSize = getFirstSize(sizes);
let video = getVideoTargetingParams(bid);
let appId = getVideoBidParam(bid, 'appId');
let bidfloor = getVideoBidFloor(bid);
let tagid = getVideoBidParam(bid, 'tagid');
let topLocation = getTopWindowLocation(bidderRequest);
let eids = getEids(bid);
let payload = {
isPrebid: true,
appId: appId,
domain: document.location.hostname,
id: utils.getUniqueIdentifierStr(),
imp: [{
video: Object.assign({
w: firstSize.w,
h: firstSize.h,
mimes: DEFAULT_MIMES
}, video),
bidfloor: bidfloor,
tagid: tagid,
secure: topLocation.protocol.indexOf('https') === 0 ? 1 : 0,
displaymanager: ADAPTER_NAME,
displaymanagerver: ADAPTER_VERSION
}],
site: {
page: topLocation.href,
domain: topLocation.hostname
},
device: {
ua: navigator.userAgent,
language: navigator.language,
devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2,
dnt: getDoNotTrack() ? 1 : 0,
js: 1,
geo: {}
},
regs: {
ext: {}
},
source: {
ext: {}
},
user: {
ext: {}
},
cur: [CURRENCY]
};
if (bidderRequest && bidderRequest.uspConsent) {
payload.regs.ext.us_privacy = bidderRequest.uspConsent;
}
if (bidderRequest && bidderRequest.gdprConsent) {
let { gdprApplies, consentString } = bidderRequest.gdprConsent;
payload.regs.ext.gdpr = gdprApplies ? 1 : 0;
payload.user.ext.consent = consentString;
}
if (bid.schain) {
payload.source.ext.schain = bid.schain;
}
if (eids.length > 0) {
payload.user.ext.eids = eids;
}
let connection = navigator.connection || navigator.webkitConnection;
if (connection && connection.effectiveType) {
payload.device.connectiontype = connection.effectiveType;
}
return payload;
}
function createBannerRequestData(bids, bidderRequest) {
let topLocation = getTopWindowLocation(bidderRequest);
let topReferrer = getTopWindowReferrer();
let slots = bids.map(bid => {
return {
slot: bid.adUnitCode,
id: getBannerBidParam(bid, 'appId'),
bidfloor: getBannerBidFloor(bid),
tagid: getBannerBidParam(bid, 'tagid'),
sizes: getBannerSizes(bid)
};
});
let payload = {
slots: slots,
page: topLocation.href,
domain: topLocation.hostname,
search: topLocation.search,
secure: topLocation.protocol.indexOf('https') === 0 ? 1 : 0,
referrer: topReferrer,
ua: navigator.userAgent,
deviceOs: getOsVersion(),
isMobile: isMobile() ? 1 : 0,
dnt: getDoNotTrack() ? 1 : 0,
adapterVersion: ADAPTER_VERSION,
adapterName: ADAPTER_NAME
};
if (bidderRequest && bidderRequest.uspConsent) {
payload.usPrivacy = bidderRequest.uspConsent;
}
if (bidderRequest && bidderRequest.gdprConsent) {
let { gdprApplies, consentString } = bidderRequest.gdprConsent;
payload.gdpr = gdprApplies ? 1 : 0;
payload.gdprConsent = consentString;
}
if (bids[0] && bids[0].schain) {
payload.schain = bids[0].schain;
}
SUPPORTED_USER_IDS.forEach(({ key, queryParam }) => {
let id = utils.deepAccess(bids, `0.userId.${key}`)
if (id) {
payload[queryParam] = id;
}
});
return payload;
}
registerBidder(spec);