modules/datawrkzBidAdapter.js
import {
deepAccess,
isArray,
getUniqueIdentifierStr,
contains,
isFn,
isPlainObject,
getBidIdParameter
} from '../src/utils.js';
import { config } from '../src/config.js';
import { Renderer } from '../src/Renderer.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { createBid } from '../src/bidfactory.js';
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
import { STATUS } from '../src/constants.js';
import { OUTSTREAM, INSTREAM } from '../src/video.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
* @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
*/
const BIDDER_CODE = 'datawrkz';
const ALIASES = [];
const ENDPOINT_URL = 'https://at.datawrkz.com/exchange/openrtb23/';
const RENDERER_URL = 'https://js.datawrkz.com/prebid/osRenderer.min.js';
const OUTSTREAM_TYPES = ['inline', 'slider_top_left', 'slider_top_right', 'slider_bottom_left', 'slider_bottom_right', 'interstitial_close', 'listicle']
const OUTSTREAM_MIMES = ['video/mp4']
const SUPPORTED_AD_TYPES = [BANNER, NATIVE, VIDEO];
export const spec = {
code: BIDDER_CODE,
aliases: ALIASES,
supportedMediaTypes: SUPPORTED_AD_TYPES,
/**
* Determines whether or not the given bid request is valid.
*
* @param {object} bid The bid to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function(bid) {
return !!(bid.params && bid.params.site_id && (deepAccess(bid, 'mediaTypes.video.context') != 'adpod'));
},
/**
* Make a server request from the list of BidRequests.
*
* @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server.
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function(validBidRequests, bidderRequest) {
let requests = [];
if (validBidRequests.length > 0) {
validBidRequests.forEach(bidRequest => {
if (!bidRequest.mediaTypes) return;
if (bidRequest.mediaTypes.banner && ((bidRequest.mediaTypes.banner.sizes && bidRequest.mediaTypes.banner.sizes.length != 0) ||
(bidRequest.sizes))) {
requests.push(buildBannerRequest(bidRequest, bidderRequest));
} else if (bidRequest.mediaTypes.native) {
requests.push(buildNativeRequest(bidRequest, bidderRequest));
} else if (bidRequest.mediaTypes.video) {
requests.push(buildVideoRequest(bidRequest, bidderRequest));
}
});
}
return requests;
},
/**
* Unpack the response from the server into a list of bids.
*
* @param {*} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function(serverResponse, request) {
var bidResponses = [];
let bidRequest = request.bidRequest
let bidResponse = serverResponse.body;
// valid object?
if ((!bidResponse || !bidResponse.id) || (!bidResponse.seatbid || bidResponse.seatbid.length === 0 ||
!bidResponse.seatbid[0].bid || bidResponse.seatbid[0].bid.length === 0)) {
return [];
}
if (getMediaTypeOfResponse(bidRequest) == BANNER) {
bidResponses = buildBannerResponse(bidRequest, bidResponse);
} else if (getMediaTypeOfResponse(bidRequest) == NATIVE) {
bidResponses = buildNativeResponse(bidRequest, bidResponse);
} else if (getMediaTypeOfResponse(bidRequest) == VIDEO) {
bidResponses = buildVideoResponse(bidRequest, bidResponse);
}
return bidResponses;
},
}
/* Generate bid request for banner adunit */
function buildBannerRequest(bidRequest, bidderRequest) {
let bidFloor = getBidFloor(bidRequest);
let adW = 0;
let adH = 0;
let bannerSizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes');
let bidSizes = isArray(bannerSizes) ? bannerSizes : bidRequest.sizes;
if (isArray(bidSizes)) {
if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') {
adW = parseInt(bidSizes[0]);
adH = parseInt(bidSizes[1]);
} else {
adW = parseInt(bidSizes[0][0]);
adH = parseInt(bidSizes[0][1]);
}
}
var deals = [];
if (bidRequest.params.deals && bidRequest.params.deals.length > 0) {
deals = bidRequest.params.deals;
}
const imp = [{
id: bidRequest.bidId,
banner: {
w: adW,
h: adH
},
bidfloor: bidFloor,
pmp: {
deals: deals
}
}];
bidRequest.requestedMediaType = BANNER;
const scriptUrl = generateScriptUrl(bidRequest);
const payloadString = generatePayload(imp, bidderRequest);
return {
method: 'POST',
url: scriptUrl,
data: payloadString,
bidRequest
};
}
/* Generate bid request for native adunit */
function buildNativeRequest(bidRequest, bidderRequest) {
let counter = 0;
let assets = [];
let bidFloor = getBidFloor(bidRequest);
let title = deepAccess(bidRequest, 'mediaTypes.native.title');
if (title && title.len) {
assets.push(generateNativeTitleObj(title, ++counter));
}
let image = deepAccess(bidRequest, 'mediaTypes.native.image');
if (image) {
assets.push(generateNativeImgObj(image, 'image', ++counter));
}
let icon = deepAccess(bidRequest, 'mediaTypes.native.icon');
if (icon) {
assets.push(generateNativeImgObj(icon, 'icon', ++counter));
}
let sponsoredBy = deepAccess(bidRequest, 'mediaTypes.native.sponsoredBy');
if (sponsoredBy) {
assets.push(generateNativeDataObj(sponsoredBy, 'sponsored', ++counter));
}
let cta = deepAccess(bidRequest, 'mediaTypes.native.cta');
if (cta) {
assets.push(generateNativeDataObj(cta, 'cta', ++counter));
}
let body = deepAccess(bidRequest, 'mediaTypes.native.body');
if (body) {
assets.push(generateNativeDataObj(body, 'desc', ++counter));
}
let request = JSON.stringify({assets: assets});
const native = {
request: request
};
var deals = [];
if (bidRequest.params.deals && bidRequest.params.deals.length > 0) {
deals = bidRequest.params.deals;
}
const imp = [{
id: bidRequest.bidId,
native: native,
bidfloor: bidFloor,
pmp: {
deals: deals
}
}];
bidRequest.requestedMediaType = NATIVE;
bidRequest.assets = assets;
const scriptUrl = generateScriptUrl(bidRequest);
const payloadString = generatePayload(imp, bidderRequest);
return {
method: 'POST',
url: scriptUrl,
data: payloadString,
bidRequest
};
}
/* Generate bid request for video adunit */
function buildVideoRequest(bidRequest, bidderRequest) {
let bidFloor = getBidFloor(bidRequest);
let sizeObj = getVideoAdUnitSize(bidRequest);
const video = {
w: sizeObj.adW,
h: sizeObj.adH,
api: deepAccess(bidRequest, 'mediaTypes.video.api'),
mimes: deepAccess(bidRequest, 'mediaTypes.video.mimes'),
protocols: deepAccess(bidRequest, 'mediaTypes.video.protocols'),
playbackmethod: deepAccess(bidRequest, 'mediaTypes.video.playbackmethod'),
minduration: deepAccess(bidRequest, 'mediaTypes.video.minduration'),
maxduration: deepAccess(bidRequest, 'mediaTypes.video.maxduration'),
startdelay: deepAccess(bidRequest, 'mediaTypes.video.startdelay'),
minbitrate: deepAccess(bidRequest, 'mediaTypes.video.minbitrate'),
maxbitrate: deepAccess(bidRequest, 'mediaTypes.video.maxbitrate'),
delivery: deepAccess(bidRequest, 'mediaTypes.video.delivery'),
linearity: deepAccess(bidRequest, 'mediaTypes.video.linearity'),
skip: deepAccess(bidRequest, 'mediaTypes.video.skip'),
skipafter: deepAccess(bidRequest, 'mediaTypes.video.skipafter')
};
let context = deepAccess(bidRequest, 'mediaTypes.video.context');
if (context == 'outstream' && !bidRequest.renderer) video.mimes = OUTSTREAM_MIMES;
var imp = [];
var deals = [];
if (bidRequest.params.deals && bidRequest.params.deals.length > 0) {
deals = bidRequest.params.deals;
}
if (context != 'adpod') {
imp.push({
id: bidRequest.bidId,
video: video,
bidfloor: bidFloor,
pmp: {
deals: deals
}
});
}
bidRequest.requestedMediaType = VIDEO;
const scriptUrl = generateScriptUrl(bidRequest);
const payloadString = generatePayload(imp, bidderRequest);
return {
method: 'POST',
url: scriptUrl,
data: payloadString,
bidRequest
};
}
/* Convert video player size to bid request compatible format */
function getVideoAdUnitSize(bidRequest) {
var adH = 0;
var adW = 0;
let playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize');
if (isArray(playerSize)) {
if (playerSize.length === 2 && typeof playerSize[0] === 'number' && typeof playerSize[1] === 'number') {
adW = parseInt(playerSize[0]);
adH = parseInt(playerSize[1]);
} else {
adW = parseInt(playerSize[0][0]);
adH = parseInt(playerSize[0][1]);
}
}
return {adH: adH, adW: adW}
}
/* Get mediatype of the adunit from request */
function getMediaTypeOfResponse(bidRequest) {
if (bidRequest.requestedMediaType == BANNER) return BANNER;
else if (bidRequest.requestedMediaType == NATIVE) return NATIVE;
else if (bidRequest.requestedMediaType == VIDEO) return VIDEO;
else return '';
}
/* Generate endpoint url */
function generateScriptUrl(bidRequest) {
let queryParams = 'hb=1';
let siteId = getBidIdParameter('site_id', bidRequest.params);
return ENDPOINT_URL + siteId + '?' + queryParams;
}
/* Generate request payload for the adunit */
function generatePayload(imp, bidderRequest) {
let domain = window.location.host;
let page = window.location.host + window.location.pathname + location.search + location.hash;
const site = {
domain: domain,
page: page,
publisher: {}
};
let regs = {ext: {}};
if (bidderRequest.uspConsent) {
regs.ext.us_privacy = bidderRequest.uspConsent;
}
if (bidderRequest.gdprConsent && typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') {
regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? '1' : '0';
}
if (config.getConfig('coppa') === true) {
regs.coppa = '1';
}
const device = {
ua: window.navigator.userAgent
};
const payload = {
id: getUniqueIdentifierStr(),
imp: imp,
site: site,
device: device,
regs: regs
};
return JSON.stringify(payload);
}
/* Generate image asset object */
function generateNativeImgObj(obj, type, id) {
let adW = 0;
let adH = 0;
let bidSizes = obj.sizes;
var typeId;
if (type == 'icon') typeId = 1;
else if (type == 'image') typeId = 3;
if (isArray(bidSizes)) {
if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') {
adW = parseInt(bidSizes[0]);
adH = parseInt(bidSizes[1]);
} else {
adW = parseInt(bidSizes[0][0]);
adH = parseInt(bidSizes[0][1]);
}
}
let required = obj.required ? 1 : 0;
let image = {
type: parseInt(typeId),
w: adW,
h: adH
};
return {
id: id,
required: required,
img: image
};
}
/* Generate title asset object */
function generateNativeTitleObj(obj, id) {
let required = obj.required ? 1 : 0;
let title = {
len: obj.len
};
return {
id: id,
required: required,
title: title
};
}
/* Generate data asset object */
function generateNativeDataObj(obj, type, id) {
var typeId;
switch (type) {
case 'sponsored': typeId = 1;
break;
case 'desc' : typeId = 2;
break;
case 'cta' : typeId = 12;
break;
}
let required = obj.required ? 1 : 0;
let data = {
type: typeId
};
if (typeId == 2 && obj.len) {
data.len = parseInt(obj.len);
}
return {
id: id,
required: required,
data: data
};
}
/* Convert banner bid response to compatible format */
function buildBannerResponse(bidRequest, bidResponse) {
const bidResponses = [];
bidResponse.seatbid[0].bid.forEach(function (bidderBid) {
let responseCPM;
let placementCode = '';
if (bidRequest) {
let bidResponse = createBid(1);
placementCode = bidRequest.placementCode;
bidRequest.status = STATUS.GOOD;
responseCPM = parseFloat(bidderBid.price);
if (responseCPM === 0 || isNaN(responseCPM)) {
let bid = createBid(2);
bid.requestId = bidRequest.bidId;
bid.bidderCode = bidRequest.bidder;
bidResponses.push(bid);
return;
}
let bidSizes = (deepAccess(bidRequest, 'mediaTypes.banner.sizes')) ? deepAccess(bidRequest, 'mediaTypes.banner.sizes') : bidRequest.sizes;
bidResponse.requestId = bidRequest.bidId;
bidResponse.placementCode = placementCode;
bidResponse.cpm = responseCPM;
bidResponse.size = bidSizes;
bidResponse.width = parseInt(bidderBid.w);
bidResponse.height = parseInt(bidderBid.h);
let responseAd = bidderBid.adm;
let responseNurl = '<img src="' + bidderBid.nurl + '" height="0px" width="0px">';
bidResponse.ad = decodeURIComponent(responseAd + responseNurl);
bidResponse.creativeId = bidderBid.id;
bidResponse.bidderCode = bidRequest.bidder;
bidResponse.ttl = 300;
bidResponse.netRevenue = true;
bidResponse.currency = 'USD';
bidResponse.mediaType = BANNER;
bidResponses.push(bidResponse);
}
});
return bidResponses;
}
/* Convert native bid response to compatible format */
function buildNativeResponse(bidRequest, response) {
const bidResponses = [];
response.seatbid[0].bid.forEach(function (bidderBid) {
let responseCPM;
let placementCode = '';
if (bidRequest) {
let bidResponse = createBid(1);
placementCode = bidRequest.placementCode;
bidRequest.status = STATUS.GOOD;
responseCPM = parseFloat(bidderBid.price);
if (responseCPM === 0 || isNaN(responseCPM)) {
let bid = createBid(2);
bid.requestId = bidRequest.bidId;
bid.bidderCode = bidRequest.bidder;
bidResponses.push(bid);
return;
}
bidResponse.requestId = bidRequest.bidId;
bidResponse.placementCode = placementCode;
bidResponse.cpm = responseCPM;
let nativeResponse = JSON.parse(bidderBid.adm).native;
const native = {
clickUrl: nativeResponse.link.url,
impressionTrackers: nativeResponse.imptrackers
};
nativeResponse.assets.forEach(function(asset) {
let keyVal = getNativeAssestObj(asset, bidRequest.assets);
native[keyVal.key] = keyVal.value;
});
bidResponse.creativeId = bidderBid.id;
bidResponse.bidderCode = bidRequest.bidder;
bidResponse.ttl = 300;
if (bidRequest.sizes) { bidResponse.size = bidRequest.sizes; }
bidResponse.netRevenue = true;
bidResponse.currency = 'USD';
bidResponse.native = native;
bidResponse.mediaType = NATIVE;
bidResponses.push(bidResponse);
}
});
return bidResponses;
}
/* Convert video bid response to compatible format */
function buildVideoResponse(bidRequest, response) {
const bidResponses = [];
response.seatbid[0].bid.forEach(function (bidderBid) {
let responseCPM;
let placementCode = '';
if (bidRequest) {
let bidResponse = createBid(1);
placementCode = bidRequest.placementCode;
bidRequest.status = STATUS.GOOD;
responseCPM = parseFloat(bidderBid.price);
if (responseCPM === 0 || isNaN(responseCPM)) {
let bid = createBid(2);
bid.requestId = bidRequest.bidId;
bid.bidderCode = bidRequest.bidder;
bidResponses.push(bid);
return;
}
let context = bidRequest.mediaTypes.video.context;
bidResponse.requestId = bidRequest.bidId;
bidResponse.placementCode = placementCode;
bidResponse.cpm = responseCPM;
let vastXml = decodeURIComponent(bidderBid.adm);
bidResponse.creativeId = bidderBid.id;
bidResponse.bidderCode = bidRequest.bidder;
bidResponse.ttl = 300;
bidResponse.netRevenue = true;
bidResponse.currency = 'USD';
var ext = bidderBid.ext;
var vastUrl = '';
if (ext) {
vastUrl = ext.vast_url;
}
var adUnitCode = bidRequest.adUnitCode;
var sizeObj = getVideoAdUnitSize(bidRequest);
bidResponse.height = sizeObj.adH;
bidResponse.width = sizeObj.adW;
switch (context) {
case OUTSTREAM:
var outstreamType = contains(OUTSTREAM_TYPES, bidRequest.params.outstreamType) ? bidRequest.params.outstreamType : '';
bidResponse.outstreamType = outstreamType;
bidResponse.ad = vastXml;
if (!bidRequest.renderer) {
const renderer = Renderer.install({
id: bidderBid.id,
url: RENDERER_URL,
config: bidRequest.params.outstreamConfig || {},
loaded: false,
adUnitCode
});
renderer.setRender(outstreamRender);
bidResponse.renderer = renderer;
} else { bidResponse.adResponse = vastXml; }
break;
case INSTREAM:
bidResponse.vastUrl = vastUrl;
bidResponse.adserverTargeting = setTargeting(vastUrl);
break;
}
bidResponse.mediaType = VIDEO;
bidResponses.push(bidResponse);
}
});
return bidResponses;
}
/* Generate renderer for outstream ad unit */
function outstreamRender(bid) {
bid.renderer.push(() => {
window.osRenderer({
adResponse: bid.ad,
height: bid.height,
width: bid.width,
targetId: bid.adUnitCode, // target div id to render video
outstreamType: bid.outstreamType,
options: bid.renderer.getConfig(),
});
});
}
/* Set targeting params used for instream video that is required to generate cache url */
function setTargeting(query) {
var targeting = {};
var hash;
var hashes = query.slice(query.indexOf('?') + 1).split('&');
for (var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
targeting['hb_' + hash[0]] = hash[1];
}
return targeting;
}
/* Get image type with respect to the id */
function getAssetImageType(id, assets) {
for (var i = 0; i < assets.length; i++) {
if (assets[i].id == id) {
if (assets[i].img.type == 1) { return 'icon'; } else if (assets[i].img.type == 3) { return 'image'; }
}
}
return '';
}
/* Get type of data asset with respect to the id */
function getAssetDataType(id, assets) {
for (var i = 0; i < assets.length; i++) {
if (assets[i].id == id) {
if (assets[i].data.type == 1) { return 'sponsored'; } else if (assets[i].data.type == 2) { return 'desc'; } else if (assets[i].data.type == 12) { return 'cta'; }
}
}
return '';
}
/* Convert response assests to compatible format */
function getNativeAssestObj(obj, assets) {
if (obj.title) {
return {
key: 'title',
value: obj.title.text
}
}
if (obj.data) {
return {
key: getAssetDataType(obj.id, assets),
value: obj.data.value
}
}
if (obj.img) {
return {
key: getAssetImageType(obj.id, assets),
value: {
url: obj.img.url,
height: obj.img.h,
width: obj.img.w
}
}
}
}
// BUILD REQUESTS: BIDFLOORS
function getBidFloor(bid) {
if (!isFn(bid.getFloor)) {
return (bid.params.bidfloor) ? bid.params.bidfloor : null;
}
let floor = bid.getFloor({
currency: 'USD',
mediaType: '*',
size: '*'
});
if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') {
return floor.floor;
}
return null;
}
registerBidder(spec);