UNPKG

mk9-prebid

Version:

Header Bidding Management Library

261 lines (232 loc) 9.01 kB
import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'gridNM'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbnm'; const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; let hasSynced = false; const LOG_ERROR_MESS = { noAdm: 'Bid from response has no adm parameter - ', noPrice: 'Bid from response has no price parameter - ', wrongContentType: 'Bid from response has wrong content_type parameter - ', noBid: 'Array of bid objects is empty', noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', emptyUids: 'Uids should be not empty', emptySeatbid: 'Seatbid array from response has empty item', emptyResponse: 'Response is empty', hasEmptySeatbidArray: 'Response has empty seatbid array', hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' }; const VIDEO_KEYS = ['mimes', 'protocols', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ VIDEO ], /** * 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) { let invalid = !bid.params.source || !utils.isStr(bid.params.source) || !bid.params.secid || !utils.isStr(bid.params.secid) || !bid.params.pubid || !utils.isStr(bid.params.pubid); const video = utils.deepAccess(bid, 'mediaTypes.video') || {}; const { protocols = video.protocols, mimes = video.mimes } = utils.deepAccess(bid, 'params.video') || {}; if (!invalid) { invalid = !protocols || !mimes; } if (!invalid) { invalid = !utils.isArray(mimes) || !mimes.length || mimes.filter((it) => !(it && utils.isStr(it))).length; if (!invalid) { invalid = !utils.isArray(protocols) || !protocols.length || protocols.filter((it) => !(utils.isNumber(it) && it > 0 && !(it % 1))).length; } } return !invalid; }, /** * 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) { const bids = validBidRequests || []; const requests = []; bids.forEach(bid => { const { params, bidderRequestId, sizes } = bid; const payload = { sizes: utils.parseSizesInput(sizes).join(','), r: bidderRequestId, wrapperType: 'Prebid_js', wrapperVersion: '$prebid.version$' }; if (bidderRequest) { if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { payload.u = bidderRequest.refererInfo.referer; } if (bidderRequest.timeout) { payload.wtimeout = bidderRequest.timeout; } if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; } payload.gdpr_applies = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; } if (bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } } const video = utils.deepAccess(bid, 'mediaTypes.video') || {}; const paramsVideo = Object.assign({}, params.video); VIDEO_KEYS.forEach((key) => { if (!(key in paramsVideo) && key in video) { paramsVideo[key] = video[key]; } }); if (!paramsVideo.size && video.playerSize && video.playerSize.length === 2) { paramsVideo.size = video.playerSize.join('x'); } if (!('mind' in paramsVideo) && 'minduration' in video) { paramsVideo.mind = video.minduration; } if (!('maxd' in paramsVideo) && 'maxduration' in video) { paramsVideo.maxd = video.maxduration; } const paramsToSend = Object.assign({}, params, {video: paramsVideo}); requests.push({ method: 'POST', url: ENDPOINT_URL + '?' + utils.parseQueryStringParameters(payload).replace(/\&$/, ''), bid: bid, data: paramsToSend // content }); }); return requests; }, /** * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. * @param {*} bidRequest * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; let errorMessage; if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; else if (serverResponse.seatbid && !serverResponse.seatbid.length) { errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; } if (!errorMessage && serverResponse.seatbid) { const serverBid = _getBidFromResponse(serverResponse.seatbid[0]); if (serverBid) { if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else if (!serverBid.price) errorMessage = LOG_ERROR_MESS.noPrice + JSON.stringify(serverBid); else if (serverBid.content_type !== 'video') errorMessage = LOG_ERROR_MESS.wrongContentType + serverBid.content_type; if (!errorMessage) { const bid = bidRequest.bid; if (!serverBid.w || !serverBid.h) { const size = utils.parseSizesInput(bid.sizes)[0].split('x'); serverBid.w = size[0]; serverBid.h = size[1]; } const bidResponse = { requestId: bid.bidId, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, creativeId: serverBid.auid || bid.bidderRequestId, currency: 'USD', netRevenue: true, ttl: TIME_TO_LIVE, dealId: serverBid.dealid, vastXml: serverBid.adm, mediaType: VIDEO, meta: { advertiserDomains: serverBid.adomain ? serverBid.adomain : [] }, adResponse: { content: serverBid.adm } }; if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { bidResponse.renderer = createRenderer(bidResponse, { id: bid.bidId, url: RENDERER_URL }); } bidResponses.push(bidResponse); } } } if (errorMessage) utils.logError(errorMessage); return bidResponses; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (!hasSynced && syncOptions.pixelEnabled) { let params = ''; if (gdprConsent && typeof gdprConsent.consentString === 'string') { if (typeof gdprConsent.gdprApplies === 'boolean') { params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; } else { params += `&gdpr_consent=${gdprConsent.consentString}`; } } if (uspConsent) { params += `&us_privacy=${uspConsent}`; } hasSynced = true; return { type: 'image', url: SYNC_URL + params }; } } }; function _getBidFromResponse(respItem) { if (!respItem) { utils.logError(LOG_ERROR_MESS.emptySeatbid); } else if (!respItem.bid) { utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); } else if (!respItem.bid[0]) { utils.logError(LOG_ERROR_MESS.noBid); } return respItem && respItem.bid && respItem.bid[0]; } function outstreamRender (bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ targetId: bid.adUnitCode, adResponse: bid.adResponse }); }); } function createRenderer (bid, rendererParams) { const renderer = Renderer.install({ id: rendererParams.id, url: rendererParams.url, loaded: false }); try { renderer.setRender(outstreamRender); } catch (err) { utils.logWarn('Prebid Error calling setRender on renderer', err); } return renderer; } export function resetUserSync() { hasSynced = false; } export function getSyncUrl() { return SYNC_URL; } registerBidder(spec);