UNPKG

mk9-prebid

Version:

Header Bidding Management Library

294 lines (251 loc) 9.32 kB
/** * This module provides [Adloox]{@link https://www.adloox.com/} Analytics * The module will inject Adloox's verification JS tag alongside slot at bidWin * @module modules/adlooxAnalyticsAdapter */ import adapterManager from '../src/adapterManager.js'; import adapter from '../src/AnalyticsAdapter.js'; import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; import { AUCTION_COMPLETED } from '../src/auction.js'; import { EVENTS } from '../src/constants.json'; import find from 'core-js-pure/features/array/find.js'; import * as utils from '../src/utils.js'; const MODULE = 'adlooxAnalyticsAdapter'; const URL_JS = 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js'; const ADLOOX_VENDOR_ID = 93; const ADLOOX_MEDIATYPE = { DISPLAY: 2, VIDEO: 6 }; const MACRO = {}; MACRO['client'] = function(b, c) { return c.client; }; MACRO['clientid'] = function(b, c) { return c.clientid; }; MACRO['tagid'] = function(b, c) { return c.tagid; }; MACRO['platformid'] = function(b, c) { return c.platformid; }; MACRO['targetelt'] = function(b, c) { return c.toselector(b); }; MACRO['creatype'] = function(b, c) { return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY; }; MACRO['pbAdSlot'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); return utils.deepAccess(adUnit, 'fpd.context.pbAdSlot') || utils.getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; }; const PARAMS_DEFAULT = { 'id1': function(b) { return b.adUnitCode }, 'id2': '%%pbAdSlot%%', 'id3': function(b) { return b.bidder }, 'id4': function(b) { return b.adId }, 'id5': function(b) { return b.dealId }, 'id6': function(b) { return b.creativeId }, 'id7': function(b) { return b.size }, 'id11': '$ADLOOX_WEBSITE' }; const NOOP = function() {}; let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { track({ eventType, args }) { if (!analyticsAdapter[`handle_${eventType}`]) return; utils.logInfo(MODULE, 'track', eventType, args); analyticsAdapter[`handle_${eventType}`](args); } }); analyticsAdapter.context = null; analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; analyticsAdapter.enableAnalytics = function(config) { analyticsAdapter.context = null; utils.logInfo(MODULE, 'config', config); if (!utils.isPlainObject(config.options)) { utils.logError(MODULE, 'missing options'); return; } if (!(config.options.js === undefined || utils.isStr(config.options.js))) { utils.logError(MODULE, 'invalid js options value'); return; } if (!(config.options.toselector === undefined || utils.isFn(config.options.toselector))) { utils.logError(MODULE, 'invalid toselector options value'); return; } if (!utils.isStr(config.options.client)) { utils.logError(MODULE, 'invalid client options value'); return; } if (!utils.isNumber(config.options.clientid)) { utils.logError(MODULE, 'invalid clientid options value'); return; } if (!utils.isNumber(config.options.tagid)) { utils.logError(MODULE, 'invalid tagid options value'); return; } if (!utils.isNumber(config.options.platformid)) { utils.logError(MODULE, 'invalid platformid options value'); return; } if (!(config.options.params === undefined || utils.isPlainObject(config.options.params))) { utils.logError(MODULE, 'invalid params options value'); return; } analyticsAdapter.context = { js: config.options.js || URL_JS, toselector: config.options.toselector || function(bid) { let code = utils.getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; // https://mathiasbynens.be/notes/css-escapes code = code.replace(/^\d/, '\\3$& '); return `#${code}` }, client: config.options.client, clientid: config.options.clientid, tagid: config.options.tagid, platformid: config.options.platformid, params: [] }; config.options.params = utils.mergeDeep({}, PARAMS_DEFAULT, config.options.params || {}); Object .keys(config.options.params) .forEach(k => { if (!Array.isArray(config.options.params[k])) { config.options.params[k] = [ config.options.params[k] ]; } config.options.params[k].forEach(v => analyticsAdapter.context.params.push([ k, v ])); }); Object.keys(COMMAND_QUEUE).forEach(commandProcess); analyticsAdapter.originEnableAnalytics(config); } analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics; analyticsAdapter.disableAnalytics = function() { analyticsAdapter.context = null; analyticsAdapter.originDisableAnalytics(); } analyticsAdapter.url = function(url, args, bid) { // utils.formatQS outputs PHP encoded querystrings... (╯°□°)╯ ┻━┻ function a2qs(a) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent function fixedEncodeURIComponent(str) { return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { return '%' + c.charCodeAt(0).toString(16); }); } const args = []; let n = a.length; while (n-- > 0) { if (!(a[n][1] === undefined || a[n][1] === null || a[n][1] === false)) { args.unshift(fixedEncodeURIComponent(a[n][0]) + (a[n][1] !== true ? ('=' + fixedEncodeURIComponent(a[n][1])) : '')); } } return args.join('&'); } const macros = (str) => { return str.replace(/%%([a-z]+)%%/gi, (match, p1) => MACRO[p1] ? MACRO[p1](bid, analyticsAdapter.context) : match); }; url = macros(url); args = args || []; let n = args.length; while (n-- > 0) { if (utils.isFn(args[n][1])) { try { args[n][1] = args[n][1](bid); } catch (_) { utils.logError(MODULE, 'macro', args[n][0], _.message); args[n][1] = `ERROR: ${_.message}`; } } if (utils.isStr(args[n][1])) { args[n][1] = macros(args[n][1]); } } return url + a2qs(args); } analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; utils.logMessage(MODULE, 'preloading verification JS'); const uri = utils.parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); const link = document.createElement('link'); link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); link.setAttribute('rel', 'preload'); link.setAttribute('as', 'script'); utils.insertElement(link); } analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { if (utils.deepAccess(bid, 'ext.adloox.video.adserver')) { utils.logMessage(MODULE, `measuring '${bid.mediaType}' ad unit code '${bid.adUnitCode}' via Ad Server module`); return; } const sl = analyticsAdapter.context.toselector(bid); let el; try { el = document.querySelector(sl); } catch (_) { } if (!el) { utils.logWarn(MODULE, `unable to find ad unit code '${bid.adUnitCode}' slot using selector '${sl}' (use options.toselector to change), ignoring`); return; } utils.logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`); const params = analyticsAdapter.context.params.concat([ [ 'tagid', '%%tagid%%' ], [ 'platform', '%%platformid%%' ], [ 'fwtype', 4 ], [ 'targetelt', '%%targetelt%%' ], [ 'creatype', '%%creatype%%' ] ]); loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox'); } adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, code: 'adloox', gvlid: ADLOOX_VENDOR_ID }); export default analyticsAdapter; // src/events.js does not support custom events or handle races... (╯°□°)╯ ┻━┻ const COMMAND_QUEUE = {}; export const COMMAND = { CONFIG: 'config', URL: 'url', TRACK: 'track' }; export function command(cmd, data, callback0) { const cid = utils.getUniqueIdentifierStr(); const callback = function() { delete COMMAND_QUEUE[cid]; if (callback0) callback0.apply(null, arguments); }; COMMAND_QUEUE[cid] = { cmd, data, callback }; if (analyticsAdapter.context) commandProcess(cid); } function commandProcess(cid) { const { cmd, data, callback } = COMMAND_QUEUE[cid]; utils.logInfo(MODULE, 'command', cmd, data); switch (cmd) { case COMMAND.CONFIG: const response = { client: analyticsAdapter.context.client, clientid: analyticsAdapter.context.clientid, tagid: analyticsAdapter.context.tagid, platformid: analyticsAdapter.context.platformid }; callback(response); break; case COMMAND.URL: if (data.ids) data.args = data.args.concat(analyticsAdapter.context.params.filter(p => /^id([1-9]|10)$/.test(p[0]))); // not >10 callback(analyticsAdapter.url(data.url, data.args, data.bid)); break; case COMMAND.TRACK: analyticsAdapter.track(data); callback(); // drain queue break; default: utils.logWarn(MODULE, 'command unknown', cmd); // do not callback as arguments are unknown and to aid debugging } }