UNPKG

mk9-prebid

Version:

Header Bidding Management Library

296 lines (259 loc) 6.95 kB
/** * This module adds reconciliation provider to the real time data module * The {@link module:modules/realTimeData} module is required * The module will add custom targetings to ad units * The module will listen to post messages from rendered creatives with Reconciliation Tag * The module will call tracking pixels to log info needed for reconciliation matching * @module modules/reconciliationRtdProvider * @requires module:modules/realTimeData */ /** * @typedef {Object} ModuleParams * @property {string} publisherMemberId * @property {?string} initUrl * @property {?string} impressionUrl * @property {?boolean} allowAccess */ import { submodule } from '../src/hook.js'; import { ajaxBuilder } from '../src/ajax.js'; import * as utils from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; /** @type {Object} */ const MessageType = { IMPRESSION_REQUEST: 'rsdk:impression:req', IMPRESSION_RESPONSE: 'rsdk:impression:res', }; /** @type {ModuleParams} */ const DEFAULT_PARAMS = { initUrl: 'https://confirm.fiduciadlt.com/init', impressionUrl: 'https://confirm.fiduciadlt.com/pimp', allowAccess: false, }; /** @type {ModuleParams} */ let _moduleParams = {}; /** * Handle postMesssage from ad creative, track impression * and send response to reconciliation ad tag * @param {Event} e */ function handleAdMessage(e) { let data = {}; let adUnitId = ''; let adDeliveryId = ''; try { data = JSON.parse(e.data); } catch (e) { return; } if (data.type === MessageType.IMPRESSION_REQUEST) { if (utils.isGptPubadsDefined()) { // 1. Find the last iframed window before window.top where the tracker was injected // (the tracker could be injected in nested iframes) const adWin = getTopIFrameWin(e.source); if (adWin && adWin !== window.top) { // 2. Find the GPT slot for the iframed window const adSlot = getSlotByWin(adWin); // 3. Get AdUnit IDs for the selected slot if (adSlot) { adUnitId = adSlot.getAdUnitPath(); adDeliveryId = adSlot.getTargeting('RSDK_ADID'); adDeliveryId = adDeliveryId.length ? adDeliveryId[0] : `${utils.timestamp()}-${utils.generateUUID()}`; } } } // Call local impression callback const args = Object.assign({}, data.args, { publisherDomain: window.location.hostname, publisherMemberId: _moduleParams.publisherMemberId, adUnitId, adDeliveryId, }); track.trackPost(_moduleParams.impressionUrl, args); // Send response back to the Advertiser tag let response = { type: MessageType.IMPRESSION_RESPONSE, id: data.id, args: Object.assign( { publisherDomain: window.location.hostname, }, data.args ), }; // If access is allowed - add ad unit id to response if (_moduleParams.allowAccess) { Object.assign(response.args, { adUnitId, adDeliveryId, }); } e.source.postMessage(JSON.stringify(response), '*'); } } /** * Get top iframe window for nested Window object * - top * -- iframe.window <-- top iframe window * --- iframe.window * ---- iframe.window <-- win * * @param {Window} win nested iframe window object * @param {Window} topWin top window */ export function getTopIFrameWin(win, topWin) { topWin = topWin || window; if (!win) { return null; } try { while (win.parent !== topWin) { win = win.parent; } return win; } catch (e) { return null; } } /** * get all slots on page * @return {Object[]} slot GoogleTag slots */ function getAllSlots() { return utils.isGptPubadsDefined() && window.googletag.pubads().getSlots(); } /** * get GPT slot by placement id * @param {string} code placement id * @return {?Object} */ function getSlotByCode(code) { const slots = getAllSlots(); if (!slots || !slots.length) { return null; } return ( find( slots, (s) => s.getSlotElementId() === code || s.getAdUnitPath() === code ) || null ); } /** * get GPT slot by iframe window * @param {Window} win * @return {?Object} */ export function getSlotByWin(win) { const slots = getAllSlots(); if (!slots || !slots.length) { return null; } return ( find(slots, (s) => { let slotElement = document.getElementById(s.getSlotElementId()); if (slotElement) { let slotIframe = slotElement.querySelector('iframe'); if (slotIframe && slotIframe.contentWindow === win) { return true; } } return false; }) || null ); } /** * Init Reconciliation post messages listeners to handle * impressions messages from ad creative */ function initListeners() { window.addEventListener('message', handleAdMessage, false); } /** * Send init event to log * @param {Array} adUnits */ function trackInit(adUnits) { track.trackPost( _moduleParams.initUrl, { adUnits, publisherDomain: window.location.hostname, publisherMemberId: _moduleParams.publisherMemberId, } ); } /** * Track event via POST request * wrap method to allow stubbing in tests * @param {string} url * @param {Object} data */ export const track = { trackPost(url, data) { const ajax = ajaxBuilder(); ajax( url, function() {}, JSON.stringify(data), { method: 'POST', } ); } } /** * Set custom targetings for provided adUnits * @param {string[]} adUnitsCodes * @return {Object} key-value object with custom targetings */ function getReconciliationData(adUnitsCodes) { const dataToReturn = {}; const adUnitsToTrack = []; adUnitsCodes.forEach((adUnitCode) => { if (!adUnitCode) { return; } const adSlot = getSlotByCode(adUnitCode); const adUnitId = adSlot ? adSlot.getAdUnitPath() : adUnitCode; const adDeliveryId = `${utils.timestamp()}-${utils.generateUUID()}`; dataToReturn[adUnitCode] = { RSDK_AUID: adUnitId, RSDK_ADID: adDeliveryId, }; adUnitsToTrack.push({ adUnitId, adDeliveryId }); }, {}); // Track init event trackInit(adUnitsToTrack); return dataToReturn; } /** @type {RtdSubmodule} */ export const reconciliationSubmodule = { /** * used to link submodule with realTimeData * @type {string} */ name: 'reconciliation', /** * get data and send back to realTimeData module * @function * @param {string[]} adUnitsCodes */ getTargetingData: getReconciliationData, init: init, }; function init(moduleConfig) { const params = moduleConfig.params; if (params && params.publisherMemberId) { _moduleParams = Object.assign({}, DEFAULT_PARAMS, params); initListeners(); } else { utils.logError('missing params for Reconciliation provider'); } return true; } submodule('realTimeData', reconciliationSubmodule);