mk9-prebid
Version:
Header Bidding Management Library
115 lines (99 loc) • 4.16 kB
JavaScript
import { config } from '../src/config.js';
import { auctionManager } from '../src/auctionManager.js';
import { INSTREAM } from '../src/video.js';
import * as events from '../src/events.js';
import * as utils from '../src/utils.js';
import { BID_STATUS, EVENTS, TARGETING_KEYS } from '../src/constants.json';
const {CACHE_ID, UUID} = TARGETING_KEYS;
const {BID_WON, AUCTION_END} = EVENTS;
const {RENDERED} = BID_STATUS;
const INSTREAM_TRACKING_DEFAULT_CONFIG = {
enabled: false,
maxWindow: 1000 * 60, // the time in ms after which polling for instream delivery stops
pollingFreq: 500 // the frequency of polling
};
// Set instreamTracking default values
config.setDefaults({
'instreamTracking': utils.deepClone(INSTREAM_TRACKING_DEFAULT_CONFIG)
});
const whitelistedResources = /video|fetch|xmlhttprequest|other/;
/**
* Here the idea is
* find all network entries via performance.getEntriesByType()
* filter it by video cache key in the url
* and exclude the ad server urls so that we dont match twice
* eg:
* dfp ads call: https://securepubads.g.doubleclick.net/gampad/ads?...hb_cache_id%3D55e85cd3-6ea4-4469-b890-84241816b131%26...
* prebid cache url: https://prebid.adnxs.com/pbc/v1/cache?uuid=55e85cd3-6ea4-4469-b890-84241816b131
*
* if the entry exists, emit the BID_WON
*
* Note: this is a workaround till a better approach is engineered.
*
* @param {Array<AdUnit>} adUnits
* @param {Array<Bid>} bidsReceived
* @param {Array<BidRequest>} bidderRequests
*
* @return {boolean} returns TRUE if tracking started
*/
export function trackInstreamDeliveredImpressions({adUnits, bidsReceived, bidderRequests}) {
const instreamTrackingConfig = config.getConfig('instreamTracking') || {};
// check if instreamTracking is enabled and performance api is available
if (!instreamTrackingConfig.enabled || !window.performance || !window.performance.getEntriesByType) {
return false;
}
// filter for video bids
const instreamBids = bidsReceived.filter(bid => {
const bidderRequest = utils.getBidRequest(bid.requestId, bidderRequests);
return bidderRequest && utils.deepAccess(bidderRequest, 'mediaTypes.video.context') === INSTREAM && bid.videoCacheKey;
});
if (!instreamBids.length) {
return false;
}
// find unique instream ad units
const instreamAdUnitMap = {};
adUnits.forEach(adUnit => {
if (!instreamAdUnitMap[adUnit.code] && utils.deepAccess(adUnit, 'mediaTypes.video.context') === INSTREAM) {
instreamAdUnitMap[adUnit.code] = true;
}
});
const instreamAdUnitsCount = Object.keys(instreamAdUnitMap).length;
const start = Date.now();
const {maxWindow, pollingFreq, urlPattern} = instreamTrackingConfig;
let instreamWinningBidsCount = 0;
let lastRead = 0; // offset for performance.getEntriesByType
function poll() {
// get network entries using the last read offset
const entries = window.performance.getEntriesByType('resource').splice(lastRead);
for (const resource of entries) {
const url = resource.name;
// check if the resource is of whitelisted resource to avoid checking img or css or script urls
if (!whitelistedResources.test(resource.initiatorType)) {
continue;
}
instreamBids.forEach((bid) => {
// match the video cache key excluding ad server call
const matches = !(url.indexOf(CACHE_ID) !== -1 || url.indexOf(UUID) !== -1) && url.indexOf(bid.videoCacheKey) !== -1;
if (urlPattern && urlPattern instanceof RegExp && !urlPattern.test(url)) {
return;
}
if (matches && bid.status !== RENDERED) {
// video found
instreamWinningBidsCount++;
auctionManager.addWinningBid(bid);
events.emit(BID_WON, bid);
}
});
}
// update offset
lastRead += entries.length;
const timeElapsed = Date.now() - start;
if (timeElapsed < maxWindow && instreamWinningBidsCount < instreamAdUnitsCount) {
setTimeout(poll, pollingFreq);
}
}
// start polling for network entries
setTimeout(poll, pollingFreq);
return true;
}
events.on(AUCTION_END, trackInstreamDeliveredImpressions)