UNPKG

mk9-prebid

Version:

Header Bidding Management Library

525 lines (472 loc) 15.4 kB
import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; const ADDITIONAL_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm 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 - ' }; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ BANNER, 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) { return !!bid.params.uid; }, /** * 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) { if (!validBidRequests.length) { return null; } let pageKeywords = null; let jwpseg = null; let permutiveseg = null; let content = null; let schain = null; let userId = null; let userIdAsEids = null; let user = null; let userExt = null; let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; const imp = []; const bidsMap = {}; validBidRequests.forEach((bid) => { if (!bidderRequestId) { bidderRequestId = bid.bidderRequestId; } if (!auctionId) { auctionId = bid.auctionId; } if (!schain) { schain = bid.schain; } if (!userId) { userId = bid.userId; } if (!userIdAsEids) { userIdAsEids = bid.userIdAsEids; } const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; bidsMap[bidId] = bid; const bidFloor = _getFloor(mediaTypes || {}, bid); if (rtd) { const jwTargeting = rtd.jwplayer && rtd.jwplayer.targeting; if (jwTargeting) { if (!jwpseg && jwTargeting.segments) { jwpseg = jwTargeting.segments; } if (!content && jwTargeting.content) { content = jwTargeting.content; } } const permutiveTargeting = rtd.p_standard && rtd.p_standard.targeting; if (!permutiveseg && permutiveTargeting && permutiveTargeting.segments) { permutiveseg = permutiveTargeting.segments; } } let impObj = { id: bidId, tagid: uid.toString(), ext: { divid: adUnitCode } }; if (!utils.isEmpty(keywords)) { if (!pageKeywords) { pageKeywords = keywords; } impObj.ext.bidder = { keywords }; } if (bidFloor) { impObj.bidfloor = bidFloor; } if (!mediaTypes || mediaTypes[BANNER]) { const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); if (banner) { impObj.banner = banner; } } if (mediaTypes && mediaTypes[VIDEO]) { const video = createVideoRequest(bid, mediaTypes[VIDEO]); if (video) { impObj.video = video; } } if (impObj.banner || impObj.video) { imp.push(impObj); } }); const source = { tid: auctionId, ext: { wrapper: 'Prebid_js', wrapper_version: '$prebid.version$' } }; if (schain) { source.ext.schain = schain; } const bidderTimeout = config.getConfig('bidderTimeout') || timeout; const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; let request = { id: bidderRequestId, site: { page: referer }, tmax, source, imp }; if (content) { request.site.content = content; } const userData = []; addSegments('iow_labs_pub_data', 'jwpseg', jwpseg, userData); addSegments('permutive', 'p_standard', permutiveseg, userData, 'permutive.com'); if (userData.length) { user = { data: userData }; } if (gdprConsent && gdprConsent.consentString) { userExt = {consent: gdprConsent.consentString}; } if (userIdAsEids && userIdAsEids.length) { userExt = userExt || {}; userExt.eids = [...userIdAsEids]; } if (userExt && Object.keys(userExt).length) { user = user || {}; user.ext = userExt; } if (user) { request.user = user; } const userKeywords = utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null; const siteKeywords = utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null; if (userKeywords) { pageKeywords = pageKeywords || {}; pageKeywords.user = pageKeywords.user || {}; pageKeywords.user.ortb2 = [ { name: 'keywords', keywords: userKeywords.split(','), } ]; } if (siteKeywords) { pageKeywords = pageKeywords || {}; pageKeywords.site = pageKeywords.site || {}; pageKeywords.site.ortb2 = [ { name: 'keywords', keywords: siteKeywords.split(','), } ]; } if (pageKeywords) { pageKeywords = reformatKeywords(pageKeywords); if (pageKeywords) { request.ext = { keywords: pageKeywords }; } } if (gdprConsent && gdprConsent.gdprApplies) { request.regs = { ext: { gdpr: gdprConsent.gdprApplies ? 1 : 0 } } } if (uspConsent) { if (!request.regs) { request.regs = {ext: {}}; } request.regs.ext.us_privacy = uspConsent; } return { method: 'POST', url: ENDPOINT_URL, data: JSON.stringify(request), bidsMap }; }, /** * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. * @param {*} bidRequest * @param {*} RendererConst * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { 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) { serverResponse.seatbid.forEach(respItem => { _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); return bidResponses; }, getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.pixelEnabled) { const syncsPerBidder = config.getConfig('userSync.syncsPerBidder'); let params = []; if (gdprConsent && typeof gdprConsent.consentString === 'string') { if (typeof gdprConsent.gdprApplies === 'boolean') { params.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); } else { params.push(`gdpr_consent=${gdprConsent.consentString}`); } } if (uspConsent) { params.push(`us_privacy=${uspConsent}`); } const stringParams = params.join('&'); const syncs = [{ type: 'image', url: ADAPTER_SYNC_URL + (stringParams ? `?${stringParams}` : '') }]; if (syncsPerBidder > 1) { syncs.push({ type: 'image', url: ADDITIONAL_SYNC_URL + (stringParams ? `&${stringParams}` : '') }); } return syncs; } } } 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 _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { const { bidsMap } = bidRequest; const bid = bidsMap[serverBid.impid]; if (!errorMessage && bid) { const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, creativeId: serverBid.auid, // bid.bidId, currency: 'USD', netRevenue: false, ttl: TIME_TO_LIVE, dealId: serverBid.dealid, meta: { advertiserDomains: serverBid.adomain ? serverBid.adomain : [] }, }; if (serverBid.content_type === 'video') { bidResponse.vastXml = serverBid.adm; bidResponse.mediaType = VIDEO; bidResponse.adResponse = { content: bidResponse.vastXml }; if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { bidResponse.renderer = createRenderer(bidResponse, { id: bid.bidId, url: RENDERER_URL }, RendererConst); } } else { bidResponse.ad = serverBid.adm; bidResponse.mediaType = BANNER; } bidResponses.push(bidResponse); } } if (errorMessage) { utils.logError(errorMessage); } } function outstreamRender (bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ targetId: bid.adUnitCode, adResponse: bid.adResponse }); }); } function createRenderer (bid, rendererParams, RendererConst) { const rendererInst = RendererConst.install({ id: rendererParams.id, url: rendererParams.url, loaded: false }); try { rendererInst.setRender(outstreamRender); } catch (err) { utils.logWarn('Prebid Error calling setRender on renderer', err); } return rendererInst; } function createVideoRequest(bid, mediaType) { const {playerSize, mimes, durationRangeSec, protocols} = mediaType; const size = (playerSize || bid.sizes || [])[0]; if (!size) return; let result = utils.parseGPTSingleSizeArrayToRtbSize(size); if (mimes) { result.mimes = mimes; } if (durationRangeSec && durationRangeSec.length === 2) { result.minduration = durationRangeSec[0]; result.maxduration = durationRangeSec[1]; } if (protocols && protocols.length) { result.protocols = protocols; } return result; } function createBannerRequest(bid, mediaType) { const sizes = mediaType.sizes || bid.sizes; if (!sizes || !sizes.length) return; let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size)); let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); if (format.length) { result.format = format } return result; } function addSegments(name, segName, segments, data, bidConfigName) { if (segments && segments.length) { data.push({ name: name, segment: segments.map((seg) => { return {name: segName, value: seg}; }) }); } else if (bidConfigName) { const configData = config.getConfig('ortb2.user.data'); let segData = null; configData && configData.some(({name, segment}) => { if (name === bidConfigName) { segData = segment; return true; } }); if (segData && segData.length) { data.push({ name: name, segment: segData.map((seg) => { return {name: segName, value: seg}; }) }); } } } function reformatKeywords(pageKeywords) { const formatedPageKeywords = {}; Object.keys(pageKeywords).forEach((name) => { const keywords = pageKeywords[name]; if (keywords) { if (name === 'site' || name === 'user') { const formatedKeywords = {}; Object.keys(keywords).forEach((pubName) => { if (Array.isArray(keywords[pubName])) { const formatedPublisher = []; keywords[pubName].forEach((pubItem) => { if (typeof pubItem === 'object' && pubItem.name) { const formatedPubItem = { name: pubItem.name, segments: [] }; Object.keys(pubItem).forEach((key) => { if (Array.isArray(pubItem[key])) { pubItem[key].forEach((keyword) => { if (keyword) { if (typeof keyword === 'string') { formatedPubItem.segments.push({ name: key, value: keyword }); } else if (key === 'segments' && typeof keyword.name === 'string' && typeof keyword.value === 'string') { formatedPubItem.segments.push(keyword); } } }); } }); if (formatedPubItem.segments.length) { formatedPublisher.push(formatedPubItem); } } }); if (formatedPublisher.length) { formatedKeywords[pubName] = formatedPublisher; } } }); formatedPageKeywords[name] = formatedKeywords; } else { formatedPageKeywords[name] = keywords; } } }); return Object.keys(formatedPageKeywords).length && formatedPageKeywords; } /** * Gets bidfloor * @param {Object} mediaTypes * @param {Object} bid * @returns {Number} floor */ function _getFloor (mediaTypes, bid) { const curMediaType = mediaTypes.video ? 'video' : 'banner'; let floor = bid.params.bidFloor || 0; if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ currency: 'USD', mediaType: curMediaType, size: bid.sizes.map(([w, h]) => ({w, h})) }); if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { floor = Math.max(floor, parseFloat(floorInfo.floor)); } } return floor; } registerBidder(spec);