mk9-prebid
Version:
Header Bidding Management Library
543 lines (506 loc) • 14.7 kB
JavaScript
/* eslint dot-notation:0, quote-props:0 */
import * as utils from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { Renderer } from '../src/Renderer.js';
const NATIVE_DEFAULTS = {
TITLE_LEN: 100,
DESCR_LEN: 200,
SPONSORED_BY_LEN: 50,
IMG_MIN: 150,
ICON_MIN: 50,
};
const DEFAULT_BID_TTL = 20;
const DEFAULT_CURRENCY = 'USD';
const DEFAULT_NET_REVENUE = true;
const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'video', 'battr', 'bcat', 'badv', 'bidfloor'];
/**
* PulsePoint Bid Adapter.
* Contact: ExchangeTeam@pulsepoint.com
*
* Aliases - pulseLite and pulsepointLite are supported for backwards compatibility.
* Formats - Display/Native/Video formats supported.
*
*/
export const spec = {
code: 'pulsepoint',
gvlid: 81,
aliases: ['pulseLite', 'pulsepointLite'],
supportedMediaTypes: ['banner', 'native', 'video'],
isBidRequestValid: bid => (
!!(bid && bid.params && bid.params.cp && bid.params.ct)
),
buildRequests: (bidRequests, bidderRequest) => {
const request = {
id: bidRequests[0].bidderRequestId,
imp: bidRequests.map(slot => impression(slot)),
site: site(bidRequests, bidderRequest),
app: app(bidRequests),
device: device(),
bcat: bidRequests[0].params.bcat,
badv: bidRequests[0].params.badv,
user: user(bidRequests[0], bidderRequest),
regs: regs(bidderRequest),
source: source(bidRequests[0].schain),
};
return {
method: 'POST',
url: 'https://bid.contextweb.com/header/ortb?src=prebid',
data: request,
bidderRequest
};
},
interpretResponse: (response, request) => (
bidResponseAvailable(request, response)
),
getUserSyncs: syncOptions => {
if (syncOptions.iframeEnabled) {
return [{
type: 'iframe',
url: 'https://bh.contextweb.com/visitormatch'
}];
} else if (syncOptions.pixelEnabled) {
return [{
type: 'image',
url: 'https://bh.contextweb.com/visitormatch/prebid'
}];
}
},
transformBidParams: function(params, isOpenRtb) {
return utils.convertTypes({
'cf': 'string',
'cp': 'number',
'ct': 'number'
}, params);
}
};
/**
* Callback for bids, after the call to PulsePoint completes.
*/
function bidResponseAvailable(request, response) {
const idToImpMap = {};
const idToBidMap = {};
const idToSlotConfig = {};
const bidResponse = response.body
// extract the request bids and the response bids, keyed by impr-id
const ortbRequest = request.data;
ortbRequest.imp.forEach(imp => {
idToImpMap[imp.id] = imp;
});
if (bidResponse) {
bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => {
idToBidMap[bid.impid] = bid;
}));
}
if (request.bidderRequest && request.bidderRequest.bids) {
request.bidderRequest.bids.forEach(bid => {
idToSlotConfig[bid.bidId] = bid;
});
}
const bids = [];
Object.keys(idToImpMap).forEach(id => {
if (idToBidMap[id]) {
const bid = {
requestId: id,
cpm: idToBidMap[id].price,
creative_id: idToBidMap[id].crid,
creativeId: idToBidMap[id].crid,
adId: id,
ttl: idToBidMap[id].exp || DEFAULT_BID_TTL,
netRevenue: DEFAULT_NET_REVENUE,
currency: bidResponse.cur || DEFAULT_CURRENCY,
meta: { advertiserDomains: idToBidMap[id].adomain || [] }
};
if (idToImpMap[id].video) {
// for outstream, a renderer is specified
if (idToSlotConfig[id] && utils.deepAccess(idToSlotConfig[id], 'mediaTypes.video.context') === 'outstream') {
bid.renderer = outstreamRenderer(utils.deepAccess(idToSlotConfig[id], 'renderer.options'), utils.deepAccess(idToBidMap[id], 'ext.outstream'));
}
bid.vastXml = idToBidMap[id].adm;
bid.mediaType = 'video';
bid.width = idToBidMap[id].w;
bid.height = idToBidMap[id].h;
} else if (idToImpMap[id].banner) {
bid.ad = idToBidMap[id].adm;
bid.width = idToBidMap[id].w || idToImpMap[id].banner.w;
bid.height = idToBidMap[id].h || idToImpMap[id].banner.h;
} else if (idToImpMap[id]['native']) {
bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]);
bid.mediaType = 'native';
}
bids.push(bid);
}
});
return bids;
}
/**
* Produces an OpenRTBImpression from a slot config.
*/
function impression(slot) {
return {
id: slot.bidId,
banner: banner(slot),
'native': nativeImpression(slot),
tagid: slot.params.ct.toString(),
video: video(slot),
bidfloor: bidFloor(slot),
ext: ext(slot),
};
}
/**
* Produces an OpenRTB Banner object for the slot given.
*/
function banner(slot) {
const sizes = parseSizes(slot);
const size = adSize(slot, sizes);
return (slot.mediaTypes && slot.mediaTypes.banner) ? {
w: size[0],
h: size[1],
battr: slot.params.battr,
format: sizes
} : null;
}
/**
* Produce openrtb format objects based on the sizes configured for the slot.
*/
function parseSizes(slot) {
const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes');
if (sizes && utils.isArray(sizes)) {
return sizes.filter(sz => utils.isArray(sz) && sz.length === 2).map(sz => ({
w: sz[0],
h: sz[1]
}));
}
return null;
}
/**
* Produces an OpenRTB Video object for the slot given
*/
function video(slot) {
if (slot.params.video) {
return Object.assign({},
slot.params.video, // previously supported as bidder param
slot.mediaTypes && slot.mediaTypes.video ? slot.mediaTypes.video : {}, // params on mediaTypes.video
{battr: slot.params.battr}
);
}
return null;
}
/**
* Unknown params are captured and sent on ext
*/
function ext(slot) {
const ext = {};
const knownParamsMap = {};
KNOWN_PARAMS.forEach(value => knownParamsMap[value] = 1);
Object.keys(slot.params).forEach(key => {
if (!knownParamsMap[key]) {
ext[key] = slot.params[key];
}
});
return Object.keys(ext).length > 0 ? { prebid: ext } : null;
}
/**
* Sets up the renderer on the bid, for outstream bid responses.
*/
function outstreamRenderer(rendererOptions, outstreamExtOptions) {
const renderer = Renderer.install({
url: outstreamExtOptions.rendererUrl,
config: {
defaultOptions: outstreamExtOptions.config,
rendererOptions,
type: outstreamExtOptions.type
},
loaded: false,
});
renderer.setRender((bid) => {
bid.renderer.push(() => {
const config = bid.renderer.getConfig();
new window.PulsePointOutstreamRenderer().render({
adUnitCode: bid.adUnitCode,
vastXml: bid.vastXml,
type: config.type,
defaultOptions: config.defaultOptions,
rendererOptions
});
});
});
return renderer;
}
/**
* Produces an OpenRTB Native object for the slot given.
*/
function nativeImpression(slot) {
if (slot.nativeParams) {
const assets = [];
addAsset(assets, titleAsset(assets.length + 1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN));
addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN));
addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN));
addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN));
addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN));
return {
request: JSON.stringify({ assets }),
ver: '1.1',
battr: slot.params.battr,
};
}
return null;
}
/**
* Helper method to add an asset to the assets list.
*/
function addAsset(assets, asset) {
if (asset) {
assets.push(asset);
}
}
/**
* Produces a Native Title asset for the configuration given.
*/
function titleAsset(id, params, defaultLen) {
if (params) {
return {
id,
required: params.required ? 1 : 0,
title: {
len: params.len || defaultLen,
},
};
}
return null;
}
/**
* Produces a Native Image asset for the configuration given.
*/
function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) {
return params ? {
id,
required: params.required ? 1 : 0,
img: {
type,
wmin: params.wmin || defaultMinWidth,
hmin: params.hmin || defaultMinHeight,
}
} : null;
}
/**
* Produces a Native Data asset for the configuration given.
*/
function dataAsset(id, params, type, defaultLen) {
return params ? {
id,
required: params.required ? 1 : 0,
data: {
type,
len: params.len || defaultLen,
}
} : null;
}
/**
* Produces an OpenRTB site object.
*/
function site(bidRequests, bidderRequest) {
const pubId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.cp : '0';
const appParams = bidRequests[0].params.app;
if (!appParams) {
return {
publisher: {
id: pubId.toString(),
},
ref: referrer(),
page: bidderRequest && bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : '',
}
}
return null;
}
/**
* Produces an OpenRTB App object.
*/
function app(bidderRequest) {
const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.cp : '0';
const appParams = bidderRequest[0].params.app;
if (appParams) {
return {
publisher: {
id: pubId.toString(),
},
bundle: appParams.bundle,
storeurl: appParams.storeUrl,
domain: appParams.domain,
}
}
return null;
}
/**
* Attempts to capture the referrer url.
*/
function referrer() {
try {
return window.top.document.referrer;
} catch (e) {
return document.referrer;
}
}
/**
* Produces an OpenRTB Device object.
*/
function device() {
return {
ua: navigator.userAgent,
language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage),
};
}
/**
* Safely parses the input given. Returns null on
* parsing failure.
*/
function parse(rawResponse) {
try {
if (rawResponse) {
return JSON.parse(rawResponse);
}
} catch (ex) {
utils.logError('pulsepointLite.safeParse', 'ERROR', ex);
}
return null;
}
/**
* Determines the AdSize for the slot.
*/
function adSize(slot, sizes) {
if (slot.params.cf) {
const size = slot.params.cf.toUpperCase().split('X');
const width = parseInt(slot.params.cw || size[0], 10);
const height = parseInt(slot.params.ch || size[1], 10);
return [width, height];
} else if (sizes && sizes.length > 0) {
return [sizes[0].w, sizes[0].h];
}
return [1, 1];
}
/**
* Handles the user level attributes and produces
* an openrtb User object.
*/
function user(bidRequest, bidderRequest) {
var ext = {};
if (bidderRequest) {
if (bidderRequest.gdprConsent) {
ext.consent = bidderRequest.gdprConsent.consentString;
}
}
if (bidRequest) {
if (bidRequest.userId) {
ext.eids = [];
addExternalUserId(ext.eids, bidRequest.userId.pubcid, 'pubcommon');
addExternalUserId(ext.eids, bidRequest.userId.britepoolid, 'britepool.com');
addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo');
addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'identityLink');
addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.id5id.uid'), 'id5-sync.com', utils.deepAccess(bidRequest, 'userId.id5id.ext'));
addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com');
// liveintent
if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) {
addExternalUserId(ext.eids, bidRequest.userId.lipb.lipbid, 'liveintent.com');
}
// TTD
addExternalUserId(ext.eids, bidRequest.userId.tdid, 'adserver.org', {
rtiPartner: 'TDID'
});
// digitrust
const digitrustResponse = bidRequest.userId.digitrustid;
if (digitrustResponse && digitrustResponse.data) {
var digitrust = {};
if (digitrustResponse.data.id) {
digitrust.id = digitrustResponse.data.id;
}
if (digitrustResponse.data.keyv) {
digitrust.keyv = digitrustResponse.data.keyv;
}
ext.digitrust = digitrust;
}
}
}
return { ext };
}
/**
* Produces external userid object in ortb 3.0 model.
*/
function addExternalUserId(eids, id, source, uidExt) {
if (id) {
var uid = { id };
if (uidExt) {
uid.ext = uidExt;
}
eids.push({
source,
uids: [ uid ]
});
}
}
/**
* Produces the regulations ortb object
*/
function regs(bidderRequest) {
if (bidderRequest.gdprConsent || bidderRequest.uspConsent) {
var ext = {};
// GDPR applies attribute (actual consent value is in user object)
if (bidderRequest.gdprConsent) {
ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0;
}
// CCPA
if (bidderRequest.uspConsent) {
ext.us_privacy = bidderRequest.uspConsent;
}
return { ext };
}
return null;
}
/**
* Creates source object with supply chain
*/
function source(schain) {
if (schain) {
return {
ext: { schain }
};
}
return null;
}
/**
* Parses the native response from the Bid given.
*/
function nativeResponse(imp, bid) {
if (imp['native']) {
const nativeAd = parse(bid.adm);
const keys = {};
if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) {
nativeAd['native'].assets.forEach(asset => {
keys.title = asset.title ? asset.title.text : keys.title;
keys.body = asset.data && asset.data.type === 2 ? asset.data.value : keys.body;
keys.sponsoredBy = asset.data && asset.data.type === 1 ? asset.data.value : keys.sponsoredBy;
keys.image = asset.img && asset.img.type === 3 ? asset.img.url : keys.image;
keys.icon = asset.img && asset.img.type === 1 ? asset.img.url : keys.icon;
});
if (nativeAd['native'].link) {
keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url);
}
keys.impressionTrackers = nativeAd['native'].imptrackers;
return keys;
}
}
return null;
}
function bidFloor(slot) {
let floor = slot.params.bidfloor;
if (utils.isFn(slot.getFloor)) {
const floorData = slot.getFloor({
mediaType: slot.mediaTypes.banner ? 'banner' : slot.mediaTypes.video ? 'video' : 'Native',
size: '*',
currency: DEFAULT_CURRENCY,
});
if (floorData && floorData.floor) {
floor = floorData.floor;
}
}
return floor;
}
registerBidder(spec);