UNPKG

mk9-prebid

Version:

Header Bidding Management Library

271 lines (251 loc) 7.36 kB
/** * This module adds browsi provider to the eal time data module * The {@link module:modules/realTimeData} module is required * The module will fetch predictions from browsi server * The module will place browsi bootstrap script on page * @module modules/browsiProvider * @requires module:modules/realTimeData */ /** * @typedef {Object} ModuleParams * @property {string} siteKey * @property {string} pubKey * @property {string} url * @property {?string} keyName */ import * as utils from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajaxBuilder} from '../src/ajax.js'; import {loadExternalScript} from '../src/adloader.js'; import {getStorageManager} from '../src/storageManager.js'; import find from 'core-js-pure/features/array/find.js'; const storage = getStorageManager(); /** @type {ModuleParams} */ let _moduleParams = {}; /** @type {null|Object} */ let _predictionsData = null; /** @type {string} */ const DEF_KEYNAME = 'browsiViewability'; /** * add browsi script to page * @param {Object} data */ export function addBrowsiTag(data) { let script = loadExternalScript(data.u, 'browsi'); script.async = true; script.setAttribute('data-sitekey', _moduleParams.siteKey); script.setAttribute('data-pubkey', _moduleParams.pubKey); script.setAttribute('prebidbpt', 'true'); script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); script.prebidData = utils.deepClone(data); if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } return script; } /** * collect required data from page * send data to browsi server to get predictions */ export function collectData() { const win = window.top; const doc = win.document; let browsiData = null; try { browsiData = storage.getDataFromLocalStorage('__brtd'); } catch (e) { utils.logError('unable to parse __brtd'); } let predictorData = { ...{ sk: _moduleParams.siteKey, sw: (win.screen && win.screen.width) || -1, sh: (win.screen && win.screen.height) || -1, url: `${doc.location.protocol}//${doc.location.host}${doc.location.pathname}`, }, ...(browsiData ? {us: browsiData} : {us: '{}'}), ...(document.referrer ? {r: document.referrer} : {}), ...(document.title ? {at: document.title} : {}) }; getPredictionsFromServer(`//${_moduleParams.url}/prebid?${toUrlParams(predictorData)}`); } export function setData(data) { _predictionsData = data; } function sendDataToModule(adUnitsCodes) { try { const _predictions = (_predictionsData && _predictionsData.p) || {}; return adUnitsCodes.reduce((rp, adUnitCode) => { if (!adUnitCode) { return rp } const adSlot = getSlotByCode(adUnitCode); const identifier = adSlot ? getMacroId(_predictionsData['pmd'], adSlot) : adUnitCode; const predictionData = _predictions[identifier]; rp[adUnitCode] = getKVObject(-1, _predictionsData['kn']); if (!predictionData) { return rp } if (predictionData.p) { if (!isIdMatchingAdUnit(adSlot, predictionData.w)) { return rp; } rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); } return rp; }, {}); } catch (e) { return {}; } } /** * get all slots on page * @return {Object[]} slot GoogleTag slots */ function getAllSlots() { return utils.isGptPubadsDefined() && window.googletag.pubads().getSlots(); } /** * get prediction and return valid object for key value set * @param {number} p * @param {string?} keyName * @return {Object} key:value */ function getKVObject(p, keyName) { const prValue = p < 0 ? 'NA' : (Math.floor(p * 10) / 10).toFixed(2); let prObject = {}; prObject[((_moduleParams['keyName'] || keyName || DEF_KEYNAME).toString())] = prValue.toString(); return prObject; } /** * check if placement id matches one of given ad units * @param {Object} slot google slot * @param {string[]} whitelist ad units * @return {boolean} */ export function isIdMatchingAdUnit(slot, whitelist) { if (!whitelist || !whitelist.length || !slot) { return true; } const slotAdUnits = slot.getAdUnitPath(); return whitelist.indexOf(slotAdUnits) !== -1; } /** * 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; } /** * generate id according to macro script * @param {Object} macro replacement macro * @param {Object} slot google slot * @return {?Object} */ export function getMacroId(macro, slot) { if (macro) { try { const macroResult = evaluate(macro, slot.getSlotElementId(), slot.getAdUnitPath(), (match, p1) => { return (p1 && slot.getTargeting(p1).join('_')) || 'NA'; }); return macroResult; } catch (e) { utils.logError(`failed to evaluate: ${macro}`); } } return slot.getSlotElementId(); } function evaluate(macro, divId, adUnit, replacer) { let macroResult = macro.p .replace(/['"]+/g, '') .replace(/<DIV_ID>/g, divId); if (adUnit) { macroResult = macroResult.replace(/<AD_UNIT>/g, adUnit); } if (replacer) { macroResult = macroResult.replace(/<KEY_(\w+)>/g, replacer); } if (macro.s) { macroResult = macroResult.substring(macro.s.s, macro.s.e); } return macroResult; } /** * XMLHttpRequest to get data form browsi server * @param {string} url server url with query params */ function getPredictionsFromServer(url) { let ajax = ajaxBuilder(); ajax(url, { success: function (response, req) { if (req.status === 200) { try { const data = JSON.parse(response); if (data && data.p && data.kn) { setData({p: data.p, kn: data.kn, pmd: data.pmd}); } else { setData({}); } addBrowsiTag(data); } catch (err) { utils.logError('unable to parse data'); setData({}) } } else if (req.status === 204) { // unrecognized site key setData({}); } }, error: function () { setData({}); utils.logError('unable to get prediction data'); } } ); } /** * serialize object and return query params string * @param {Object} data * @return {string} */ function toUrlParams(data) { return Object.keys(data) .map(key => key + '=' + encodeURIComponent(data[key])) .join('&'); } /** @type {RtdSubmodule} */ export const browsiSubmodule = { /** * used to link submodule with realTimeData * @type {string} */ name: 'browsi', /** * get data and send back to realTimeData module * @function * @param {string[]} adUnitsCodes */ getTargetingData: sendDataToModule, init: init, }; function init(moduleConfig) { _moduleParams = moduleConfig.params; if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { collectData(); } else { utils.logError('missing params for Browsi provider'); } return true; } function registerSubModule() { submodule('realTimeData', browsiSubmodule); } registerSubModule();