modules/mediaforceBidAdapter.js
import { getDNT, deepAccess, isStr, replaceAuctionPrice, triggerPixel, parseGPTSingleSizeArrayToRtbSize, isEmpty } from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, NATIVE} from '../src/mediaTypes.js';
import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
* @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
* @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse
*/
const BIDDER_CODE = 'mediaforce';
const ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid';
const TEST_ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid?debug_key=abcdefghijklmnop';
const NATIVE_ID_MAP = {};
const NATIVE_PARAMS = {
title: {
id: 1,
name: 'title'
},
icon: {
id: 2,
type: 1,
name: 'img'
},
image: {
id: 3,
type: 3,
name: 'img'
},
body: {
id: 4,
name: 'data',
type: 2
},
sponsoredBy: {
id: 5,
name: 'data',
type: 1
},
cta: {
id: 6,
type: 12,
name: 'data'
},
body2: {
id: 7,
name: 'data',
type: 10
},
rating: {
id: 8,
name: 'data',
type: 3
},
likes: {
id: 9,
name: 'data',
type: 4
},
downloads: {
id: 10,
name: 'data',
type: 5
},
displayUrl: {
id: 11,
name: 'data',
type: 11
},
price: {
id: 12,
name: 'data',
type: 6
},
salePrice: {
id: 13,
name: 'data',
type: 7
},
address: {
id: 14,
name: 'data',
type: 9
},
phone: {
id: 15,
name: 'data',
type: 8
}
};
Object.keys(NATIVE_PARAMS).forEach((key) => {
NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key;
});
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, NATIVE],
/**
* Determines whether or not the given bid request is valid.
*
* @param {BidRequest} bid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function(bid) {
return !!((typeof bid.params === 'object') && bid.params.placement_id && bid.params.publisher_id);
},
/**
* Make a server request from the list of BidRequests.
*
* @param {BidRequest[]} validBidRequests - an array of bids
* @param {bidderRequest} bidderRequest bidder request object
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function(validBidRequests, bidderRequest) {
// convert Native ORTB definition to old-style prebid native definition
validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests);
if (validBidRequests.length === 0) {
return;
}
// TODO: is 'ref' the right value here?
const referer = bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.ref) : '';
const auctionId = bidderRequest && bidderRequest.auctionId;
const timeout = bidderRequest && bidderRequest.timeout;
const dnt = getDNT() ? 1 : 0;
const requestsMap = {};
const requests = [];
let isTest = false;
validBidRequests.forEach(bid => {
isTest = isTest || bid.params.is_test;
let tagid = bid.params.placement_id;
let bidfloor = 0;
let validImp = false;
let impObj = {
id: bid.bidId,
tagid: tagid,
secure: window.location.protocol === 'https:' ? 1 : 0,
bidfloor: bidfloor,
ext: {
mediaforce: {
transactionId: bid.ortb2Imp?.ext?.tid,
}
}
};
for (let mediaTypes in bid.mediaTypes) {
switch (mediaTypes) {
case BANNER:
impObj.banner = createBannerRequest(bid);
validImp = true;
break;
case NATIVE:
impObj.native = createNativeRequest(bid);
validImp = true;
break;
default: return;
}
}
let request = requestsMap[bid.params.publisher_id];
if (!request) {
request = {
id: Math.round(Math.random() * 1e16).toString(16),
site: {
// TODO: this should probably look at refererInfo
page: window.location.href,
ref: referer,
id: bid.params.publisher_id,
publisher: {
id: bid.params.publisher_id
},
},
device: {
ua: navigator.userAgent,
js: 1,
dnt: dnt,
language: getLanguage()
},
ext: {
mediaforce: {
// TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
hb_key: auctionId
}
},
tmax: timeout,
imp: []
};
requestsMap[bid.params.publisher_id] = request;
requests.push({
method: 'POST',
url: ENDPOINT_URL,
data: request
});
}
validImp && request.imp.push(impObj);
});
requests.forEach((req) => {
if (isTest) {
req.url = TEST_ENDPOINT_URL;
}
req.data = JSON.stringify(req.data);
});
return requests;
},
/**
* Unpack the response from the server into a list of bids.
*
* @param {ServerResponse} serverResponse A successful response from the server.
* @param {BidRequest} bidRequest
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function(serverResponse, bidRequest) {
if (!serverResponse || !serverResponse.body) {
return [];
}
const responseBody = serverResponse.body;
const bidResponses = [];
const cur = responseBody.cur;
responseBody.seatbid.forEach((bids) => {
bids.bid.forEach((serverBid) => {
const bid = {
requestId: serverBid.impid,
cpm: parseFloat(serverBid.price),
creativeId: serverBid.adid,
currency: cur,
netRevenue: true,
ttl: serverBid.ttl || 300,
meta: {
advertiserDomains: serverBid.adomain ? serverBid.adomain : []
},
burl: serverBid.burl,
};
if (serverBid.dealid) {
bid.dealId = serverBid.dealid;
}
let jsonAdm;
let adm = serverBid.adm;
let ext = serverBid.ext;
try {
jsonAdm = JSON.parse(adm);
} catch (err) {}
if (jsonAdm && jsonAdm.native) {
ext = ext || {};
ext.native = jsonAdm.native;
adm = null;
}
if (adm) {
bid.width = serverBid.w;
bid.height = serverBid.h;
bid.ad = adm;
bid.mediaType = BANNER;
} else if (ext && ext.native) {
bid.native = parseNative(ext.native);
bid.mediaType = NATIVE;
}
bidResponses.push(bid);
})
});
return bidResponses;
},
/**
* Register bidder specific code, which will execute if a bid from this bidder won the auction
* @param {Bid} The bid that won the auction
*/
onBidWon: function(bid) {
const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || '';
if (isStr(bid.burl) && bid.burl !== '') {
bid.burl = replaceAuctionPrice(bid.burl, bid.originalCpm || cpm);
triggerPixel(bid.burl);
}
},
};
registerBidder(spec);
function getLanguage() {
const language = navigator.language ? 'language' : 'userLanguage';
return navigator[language].split('-')[0];
}
function createBannerRequest(bid) {
const sizes = bid.mediaTypes.banner.sizes;
if (!sizes.length) return;
let format = [];
let r = parseGPTSingleSizeArrayToRtbSize(sizes[0]);
for (let f = 1; f < sizes.length; f++) {
format.push(parseGPTSingleSizeArrayToRtbSize(sizes[f]));
}
if (format.length) {
r.format = format
}
return r
}
function parseNative(native) {
const {assets, link, imptrackers, jstracker} = native;
const result = {
clickUrl: link.url,
clickTrackers: link.clicktrackers || [],
impressionTrackers: imptrackers || [],
javascriptTrackers: jstracker ? [jstracker] : []
};
(assets || []).forEach((asset) => {
const {id, img, data, title} = asset;
const key = NATIVE_ID_MAP[id];
if (key) {
if (!isEmpty(title)) {
result.title = title.text
} else if (!isEmpty(img)) {
result[key] = {
url: img.url,
height: img.h,
width: img.w
}
} else if (!isEmpty(data)) {
result[key] = data.value;
}
}
});
return result;
}
function createNativeRequest(bid) {
const assets = [];
if (bid.nativeParams) {
Object.keys(bid.nativeParams).forEach((key) => {
if (NATIVE_PARAMS[key]) {
const {name, type, id} = NATIVE_PARAMS[key];
const assetObj = type ? {type} : {};
let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key];
if (len) {
assetObj.len = len;
}
if (aRatios && aRatios[0]) {
aRatios = aRatios[0];
let wmin = aRatios.min_width || 0;
let hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0;
assetObj.wmin = wmin;
assetObj.hmin = hmin;
}
if (sizes && sizes.length) {
sizes = [].concat(...sizes);
assetObj.w = sizes[0];
assetObj.h = sizes[1];
}
const asset = {required: required ? 1 : 0, id};
asset[name] = assetObj;
assets.push(asset);
}
});
}
return {
ver: '1.2',
request: {
assets: assets,
context: 1,
plcmttype: 1,
ver: '1.2'
}
}
}