UNPKG

mk9-prebid

Version:

Header Bidding Management Library

493 lines (427 loc) 13.6 kB
import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import find from 'core-js-pure/features/array/find.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; const VERSION = '1.0'; const BIDDER_CODE = 'adyoulike'; const DEFAULT_DC = 'hb-api'; const CURRENCY = 'USD'; const NATIVE_IMAGE = { image: { required: true }, title: { required: true }, sponsoredBy: { required: true }, clickUrl: { required: true }, body: { required: false }, icon: { required: false }, cta: { required: false } }; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE, VIDEO], aliases: ['ayl'], // short code /** * 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) { const sizes = getSize(getSizeArray(bid)); const sizeValid = sizes.width > 0 && sizes.height > 0; // allows no size for native only return (bid.params && bid.params.placement && (sizeValid || (bid.mediaTypes && bid.mediaTypes.native))); }, /** * Make a server request from the list of BidRequests. * * @param {bidRequests} - bidRequests.bids[] is an array of AdUnits and bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { const payload = { Version: VERSION, Bids: bidRequests.reduce((accumulator, bidReq) => { let mediatype = getMediatype(bidReq); let sizesArray = getSizeArray(bidReq); let size = getSize(sizesArray); accumulator[bidReq.bidId] = {}; accumulator[bidReq.bidId].PlacementID = bidReq.params.placement; accumulator[bidReq.bidId].TransactionID = bidReq.transactionId; accumulator[bidReq.bidId].Width = size.width; accumulator[bidReq.bidId].Height = size.height; accumulator[bidReq.bidId].AvailableSizes = sizesArray.join(','); if (typeof bidReq.getFloor === 'function') { accumulator[bidReq.bidId].Pricing = getFloor(bidReq, size, mediatype); } if (mediatype === NATIVE) { let nativeReq = bidReq.mediaTypes.native; if (nativeReq.type === 'image') { nativeReq = Object.assign({}, NATIVE_IMAGE, nativeReq); } // click url is always mandatory even if not specified by publisher nativeReq.clickUrl = { required: true }; accumulator[bidReq.bidId].Native = nativeReq; } if (mediatype === VIDEO) { accumulator[bidReq.bidId].Video = bidReq.mediaTypes.video; const size = bidReq.mediaTypes.video.playerSize; if (Array.isArray(size) && !Array.isArray(size[0])) { accumulator[bidReq.bidId].Video.playerSize = [size]; } } return accumulator; }, {}), PageRefreshed: getPageRefreshed() }; if (bidderRequest && bidderRequest.gdprConsent) { payload.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : null }; } if (bidderRequest && bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } const data = JSON.stringify(payload); const options = { withCredentials: true }; return { method: 'POST', url: createEndpoint(bidRequests, bidderRequest), data, options }; }, /** * 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) { const bidResponses = []; var bidRequests = {}; try { bidRequests = JSON.parse(request.data).Bids; } catch (err) { // json error initial request can't be read } // For this adapter, serverResponse is a list serverResponse.body.forEach(response => { const bid = createBid(response, bidRequests); if (bid) { bidResponses.push(bid); } }); return bidResponses; } } /* Get hostname from bids */ function getHostname(bidderRequest) { let dcHostname = find(bidderRequest, bid => bid.params.DC); if (dcHostname) { return ('-' + dcHostname.params.DC); } return ''; } /* Get current page canonical url */ function getCanonicalUrl() { let link; if (window.self !== window.top) { try { link = window.top.document.head.querySelector('link[rel="canonical"][href]'); } catch (e) { } } else { link = document.head.querySelector('link[rel="canonical"][href]'); } if (link) { return link.href; } return ''; } /* Get mediatype from bidRequest */ function getMediatype(bidRequest) { if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { return VIDEO; } else if (utils.deepAccess(bidRequest, 'mediaTypes.banner')) { return BANNER; } else if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { return NATIVE; } } /* Get Floor price information */ function getFloor(bidRequest, size, mediaType) { const bidFloors = bidRequest.getFloor({ currency: CURRENCY, mediaType, size: [ size.width, size.height ] }); if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) { return bidFloors.floor; } } /* Get information on page refresh */ function getPageRefreshed() { try { if (performance && performance.navigation) { return performance.navigation.type === performance.navigation.TYPE_RELOAD; } } catch (e) { } return false; } /* Create endpoint url */ function createEndpoint(bidRequests, bidderRequest) { let host = getHostname(bidRequests); return utils.buildUrl({ protocol: 'https', host: `${DEFAULT_DC}${host}.omnitagjs.com`, pathname: '/hb-api/prebid/v1', search: createEndpointQS(bidderRequest) }); } /* Create endpoint query string */ function createEndpointQS(bidderRequest) { const qs = {}; if (bidderRequest) { const ref = bidderRequest.refererInfo; if (ref) { qs.RefererUrl = encodeURIComponent(ref.referer); if (ref.numIframes > 0) { qs.SafeFrame = true; } } } const can = getCanonicalUrl(); if (can) { qs.CanonicalUrl = encodeURIComponent(can); } const domain = config.getConfig('publisherDomain'); if (domain) { qs.PublisherDomain = encodeURIComponent(domain); } return qs; } function getSizeArray(bid) { let inputSize = bid.sizes || []; if (bid.mediaTypes && bid.mediaTypes.banner) { inputSize = bid.mediaTypes.banner.sizes || []; } // handle size in bid.params in formats: [w, h] and [[w,h]]. if (bid.params && Array.isArray(bid.params.size)) { inputSize = bid.params.size; if (!Array.isArray(inputSize[0])) { inputSize = [inputSize] } } return utils.parseSizesInput(inputSize); } /* Get parsed size from request size */ function getSize(sizesArray) { const parsed = {}; // the main requested size is the first one const size = sizesArray[0]; if (typeof size !== 'string') { return parsed; } const parsedSize = size.toUpperCase().split('X'); const width = parseInt(parsedSize[0], 10); if (width) { parsed.width = width; } const height = parseInt(parsedSize[1], 10); if (height) { parsed.height = height; } return parsed; } function getInternalImgUrl(uid) { if (!uid) return ''; return 'https://blobs.omnitagjs.com/blobs/' + uid.substr(16, 2) + '/' + uid.substr(16) + '/' + uid; } function getImageUrl(config, resource, width, height) { let url = ''; if (resource && resource.Kind) { switch (resource.Kind) { case 'INTERNAL': url = getInternalImgUrl(resource.Data.Internal.BlobReference.Uid); break; case 'EXTERNAL': const dynPrefix = config.DynamicPrefix; let extUrl = resource.Data.External.Url; extUrl = extUrl.replace(/\[height\]/i, '' + height); extUrl = extUrl.replace(/\[width\]/i, '' + width); if (extUrl.indexOf(dynPrefix) >= 0) { const urlmatch = (/.*url=([^&]*)/gm).exec(extUrl); url = urlmatch ? urlmatch[1] : ''; if (!url) { url = getInternalImgUrl((/.*key=([^&]*)/gm).exec(extUrl)[1]); } } else { url = extUrl; } break; } } return url; } function getTrackers(eventsArray, jsTrackers) { const result = []; if (!eventsArray) return result; eventsArray.map((item, index) => { if ((jsTrackers && item.Kind === 'JAVASCRIPT_URL') || (!jsTrackers && item.Kind === 'PIXEL_URL')) { result.push(item.Url); } }); return result; } function getVideoAd(response) { var adJson = {}; if (typeof response.Ad === 'string') { adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]); return utils.deepAccess(adJson, 'Content.MainVideo.Vast'); } } function getNativeAssets(response, nativeConfig) { if (typeof response.Native === 'object') { return response.Native; } const native = {}; var adJson = {}; var textsJson = {}; if (typeof response.Ad === 'string') { adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]); textsJson = adJson.Content.Preview.Text; var impressionUrl = adJson.TrackingPrefix + '/pixel?event_kind=IMPRESSION&attempt=' + adJson.Attempt; var insertionUrl = adJson.TrackingPrefix + '/pixel?event_kind=INSERTION&attempt=' + adJson.Attempt; if (adJson.Campaign) { impressionUrl += '&campaign=' + adJson.Campaign; insertionUrl += '&campaign=' + adJson.Campaign; } native.clickUrl = adJson.TrackingPrefix + '/ar?event_kind=CLICK&attempt=' + adJson.Attempt + '&campaign=' + adJson.Campaign + '&url=' + encodeURIComponent(adJson.Content.Landing.Url); if (adJson.OnEvents) { native.clickTrackers = getTrackers(adJson.OnEvents['CLICK']); native.impressionTrackers = getTrackers(adJson.OnEvents['IMPRESSION']); native.javascriptTrackers = getTrackers(adJson.OnEvents['IMPRESSION'], true); } else { native.impressionTrackers = []; } native.impressionTrackers.push(impressionUrl, insertionUrl); } Object.keys(nativeConfig).map(function(key, index) { switch (key) { case 'title': native[key] = textsJson.TITLE; break; case 'body': native[key] = textsJson.DESCRIPTION; break; case 'cta': native[key] = textsJson.CALLTOACTION; break; case 'sponsoredBy': native[key] = adJson.Content.Preview.Sponsor.Name; break; case 'image': // main image requested size const imgSize = nativeConfig.image.sizes || []; if (!imgSize.length) { imgSize[0] = response.Width || 300; imgSize[1] = response.Height || 250; } const url = getImageUrl(adJson, utils.deepAccess(adJson, 'Content.Preview.Thumbnail.Image'), imgSize[0], imgSize[1]); if (url) { native[key] = { url, width: imgSize[0], height: imgSize[1] }; } break; case 'icon': // icon requested size const iconSize = nativeConfig.icon.sizes || []; if (!iconSize.length) { iconSize[0] = 50; iconSize[1] = 50; } const icurl = getImageUrl(adJson, utils.deepAccess(adJson, 'Content.Preview.Sponsor.Logo.Resource'), iconSize[0], iconSize[1]); if (url) { native[key] = { url: icurl, width: iconSize[0], height: iconSize[1] }; } break; case 'privacyIcon': native[key] = getImageUrl(adJson, utils.deepAccess(adJson, 'Content.Preview.Credit.Logo.Resource'), 25, 25); break; case 'privacyLink': native[key] = utils.deepAccess(adJson, 'Content.Preview.Credit.Url'); break; } }); return native; } /* Create bid from response */ function createBid(response, bidRequests) { if (!response || (!response.Ad && !response.Native && !response.Vast)) { return } const request = bidRequests && bidRequests[response.BidID]; // In case we don't retreive the size from the adserver, use the given one. if (request) { if (!response.Width || response.Width === '0') { response.Width = request.Width; } if (!response.Height || response.Height === '0') { response.Height = request.Height; } } const bid = { requestId: response.BidID, ttl: 3600, creativeId: response.CreativeID, cpm: response.Price, netRevenue: true, currency: CURRENCY, meta: response.Meta || { advertiserDomains: [] } }; if (request && request.Native) { bid.native = getNativeAssets(response, request.Native); bid.mediaType = 'native'; } else if (request && request.Video) { const vast64 = response.Vast || getVideoAd(response); bid.vastXml = vast64 ? window.atob(vast64) : ''; bid.mediaType = 'video'; } else { bid.width = response.Width; bid.height = response.Height; bid.ad = response.Ad; } return bid; } registerBidder(spec);