Skip to content

Commit

Permalink
move vast tracking logic out of core
Browse files Browse the repository at this point in the history
  • Loading branch information
dgirardi committed Dec 7, 2023
1 parent cb593d5 commit a09dd88
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 143 deletions.
95 changes: 95 additions & 0 deletions libraries/vastTrackers/vastTrackers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {addBidResponse} from '../../src/auction.js';
import {VIDEO} from '../../src/mediaTypes.js';
import {logError} from '../../src/utils.js';
import {isActivityAllowed} from '../../src/activities/rules.js';
import {ACTIVITY_REPORT_ANALYTICS} from '../../src/activities/activities.js';
import {activityParams} from '../../src/activities/activityParams.js';

const vastTrackers = [];

addBidResponse.before(function (next, adUnitcode, bidResponse, reject) {
if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) {
const vastTrackers = getVastTrackers(bidResponse);
if (vastTrackers) {
bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml);
const impTrackers = vastTrackers.get('impressions');
if (impTrackers) {
bidResponse.vastImpUrl = [].concat(impTrackers).concat(bidResponse.vastImpUrl).filter(t => t);
}
}
}
next(adUnitcode, bidResponse, reject);
});

export function registerVastTrackers(moduleType, moduleName, trackerFn) {
if (typeof trackerFn === 'function') {
vastTrackers.push({'moduleType': moduleType, 'moduleName': moduleName, 'trackerFn': trackerFn});
}
}

export function insertVastTrackers(trackers, vastXml) {
const doc = new DOMParser().parseFromString(vastXml, 'text/xml');
const wrappers = doc.querySelectorAll('VAST Ad Wrapper, VAST Ad InLine');
try {
if (wrappers.length) {
wrappers.forEach(wrapper => {
if (trackers.get('impressions')) {
trackers.get('impressions').forEach(trackingUrl => {
const impression = doc.createElement('Impression');
impression.appendChild(doc.createCDATASection(trackingUrl));
wrapper.appendChild(impression);
});
}
});
vastXml = new XMLSerializer().serializeToString(doc);
}
} catch (error) {
logError('an error happened trying to insert trackers in vastXml');
}
return vastXml;
}

export function getVastTrackers(bid) {
let trackers = [];
vastTrackers.filter(
({
moduleType,
moduleName,
trackerFn
}) => isActivityAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(moduleType, moduleName))
).forEach(({trackerFn}) => {
let trackersToAdd = trackerFn(bid);
trackersToAdd.forEach(trackerToAdd => {
if (isValidVastTracker(trackers, trackerToAdd)) {
trackers.push(trackerToAdd);
}
});
});
const trackersMap = trackersToMap(trackers);
return (trackersMap.size ? trackersMap : null);
};

function isValidVastTracker(trackers, trackerToAdd) {
return trackerToAdd.hasOwnProperty('event') && trackerToAdd.hasOwnProperty('url');
}

function trackersToMap(trackers) {
return trackers.reduce((map, {url, event}) => {
!map.has(event) && map.set(event, new Set());
map.get(event).add(url);
return map;
}, new Map());
}

export function addImpUrlToTrackers(bid, trackersMap) {
if (bid.vastImpUrl) {
if (!trackersMap) {
trackersMap = new Map();
}
if (!trackersMap.get('impressions')) {
trackersMap.set('impressions', new Set());
}
trackersMap.get('impressions').add(bid.vastImpUrl);
}
return trackersMap;
}
6 changes: 1 addition & 5 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import {config} from './config.js';
import {userSync} from './userSync.js';
import {hook} from './hook.js';
import {find, includes} from './polyfill.js';
import {OUTSTREAM, getVastTrackers, insertVastTrackers} from './video.js';
import {OUTSTREAM} from './video.js';
import {VIDEO} from './mediaTypes.js';
import {auctionManager} from './auctionManager.js';
import {bidderSettings} from './bidderSettings.js';
Expand Down Expand Up @@ -582,11 +582,7 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au
}), 'video');
const context = videoMediaType && deepAccess(videoMediaType, 'context');
const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey');
const vastTrackers = getVastTrackers(bidResponse);

if (vastTrackers) {
bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml);
}
if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) {
if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) {
addBid = false;
Expand Down
74 changes: 3 additions & 71 deletions src/video.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import adapterManager from './adapterManager.js';
import { deepAccess, logError } from './utils.js';
import { config } from '../src/config.js';
import {deepAccess, logError} from './utils.js';
import {config} from '../src/config.js';
import {includes} from './polyfill.js';
import { hook } from './hook.js';
import {hook} from './hook.js';
import {auctionManager} from './auctionManager.js';
import {isActivityAllowed} from './activities/rules.js';
import {activityParams} from './activities/activityParams.js';
import {ACTIVITY_REPORT_ANALYTICS} from './activities/activities.js';

const VIDEO_MEDIA_TYPE = 'video';
export const OUTSTREAM = 'outstream';
Expand All @@ -24,8 +21,6 @@ export const videoBidder = bid => includes(adapterManager.videoAdapters, bid.bid
export const hasNonVideoBidder = adUnit =>
adUnit.bids.filter(bid => !videoBidder(bid)).length;

let vastTrackers = [];

/**
* @typedef {object} VideoBid
* @property {string} adId id of the bid
Expand Down Expand Up @@ -69,66 +64,3 @@ export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaT

return true;
}, 'checkVideoBidSetup');

export function registerVastTrackers(moduleType, moduleName, trackerFn) {
if (typeof trackerFn === 'function') {
vastTrackers.push({'moduleType': moduleType, 'moduleName': moduleName, 'trackerFn': trackerFn});
}
}

export function insertVastTrackers(trackers, vastXml) {
const doc = new DOMParser().parseFromString(vastXml, 'text/xml');
const wrappers = doc.querySelectorAll('VAST Ad Wrapper, VAST Ad InLine');
try {
if (wrappers.length) {
wrappers.forEach(wrapper => {
if (trackers.get('impressions')) {
trackers.get('impressions').forEach(trackingUrl => {
const impression = doc.createElement('Impression');
impression.appendChild(doc.createCDATASection(trackingUrl));
wrapper.appendChild(impression)
});
}
});
vastXml = new XMLSerializer().serializeToString(doc);
}
} catch (error) {
logError('an error happened trying to insert trackers in vastXml');
}
return vastXml;
}

export function getVastTrackers(bid) {
let trackers = [];
vastTrackers.filter(
({moduleType, moduleName, trackerFn}) => isActivityAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(moduleType, moduleName))
).forEach(({trackerFn}) => {
let trackersToAdd = trackerFn(bid);
trackersToAdd.forEach(trackerToAdd => {
if (isValidVastTracker(trackers, trackerToAdd)) { trackers.push(trackerToAdd); }
});
});
const trackersMap = trackersToMap(trackers);
return (trackersMap.size ? trackersMap : null);
};

function isValidVastTracker(trackers, trackerToAdd) {
return trackerToAdd.hasOwnProperty('event') && trackerToAdd.hasOwnProperty('url');
}

function trackersToMap(trackers) {
return trackers.reduce((map, {url, event}) => {
!map.has(event) && map.set(event, new Set())
map.get(event).add(url);
return map;
}, new Map())
}

export function addImpUrlToTrackers(bid, trackersMap) {
if (bid.vastImpUrl) {
if (!trackersMap) { trackersMap = new Map(); }
if (!trackersMap.get('impressions')) { trackersMap.set('impressions', new Set()); }
trackersMap.get('impressions').add(bid.vastImpUrl);
}
return trackersMap;
}
13 changes: 4 additions & 9 deletions src/videoCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import {ajaxBuilder} from './ajax.js';
import {config} from './config.js';
import {auctionManager} from './auctionManager.js';
import {getVastTrackers, addImpUrlToTrackers} from './video.js';

/**
* Might be useful to be configurable in the future
Expand Down Expand Up @@ -43,16 +42,12 @@ const ttlBufferInSeconds = 15;
* @param {string} impUrl An impression tracker URL for the delivery of the video ad
* @return A VAST URL which loads XML from the given URI.
*/
function wrapURI(uri, vastTrackers) {
function wrapURI(uri, impTrackerURLs) {
impTrackerURLs = impTrackerURLs && (Array.isArray(impTrackerURLs) ? impTrackerURLs : [impTrackerURLs]);
// Technically, this is vulnerable to cross-script injection by sketchy vastUrl bids.
// We could make sure it's a valid URI... but since we're loading VAST XML from the
// URL they provide anyway, that's probably not a big deal.
let impressions = '';
if (vastTrackers && vastTrackers.get('impressions')) {
vastTrackers.get('impressions').forEach(trackingImp => {
impressions += `<Impression><![CDATA[${trackingImp}]]></Impression>`;
});
}
let impressions = impTrackerURLs ? impTrackerURLs.map(trk => `<Impression><![CDATA[${trk}]]></Impression>`).join('') : '';
return `<VAST version="3.0">
<Ad>
<Wrapper>
Expand All @@ -73,7 +68,7 @@ function wrapURI(uri, vastTrackers) {
* @param index
*/
function toStorageRequest(bid, {index = auctionManager.index} = {}) {
const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, addImpUrlToTrackers(bid, getVastTrackers(bid)));
const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl);
const auction = index.getAuction(bid);
const ttlWithBuffer = Number(bid.ttl) + ttlBufferInSeconds;
let payload = {
Expand Down
33 changes: 33 additions & 0 deletions test/spec/libraries/vastTrackers_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {addImpUrlToTrackers, getVastTrackers, insertVastTrackers, registerVastTrackers} from 'libraries/vastTrackers/vastTrackers.js';
import {MODULE_TYPE_ANALYTICS} from '../../../src/activities/modules.js';

describe('vast trackers', () => {
it('insert into tracker list', function() {
let trackers = getVastTrackers({'cpm': 1.0});
if (!trackers || !trackers.get('impressions')) {
registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) {
return [
{'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`}
];
});
}
trackers = getVastTrackers({'cpm': 1.0});
expect(trackers).to.be.a('map');
expect(trackers.get('impressions')).to.exists;
expect(trackers.get('impressions').has('https://vasttracking.mydomain.com/vast?cpm=1')).to.be.true;
});

it('insert trackers in vastXml', function() {
const trackers = getVastTrackers({'cpm': 1.0});
let vastXml = '<VAST><Ad><Wrapper></Wrapper></Ad></VAST>';
vastXml = insertVastTrackers(trackers, vastXml);
expect(vastXml).to.equal('<VAST><Ad><Wrapper><Impression><![CDATA[https://vasttracking.mydomain.com/vast?cpm=1]]></Impression></Wrapper></Ad></VAST>');
});

it('test addImpUrlToTrackers', function() {
const trackers = addImpUrlToTrackers({'vastImpUrl': 'imptracker.com'}, getVastTrackers({'cpm': 1.0}));
expect(trackers).to.be.a('map');
expect(trackers.get('impressions')).to.exists;
expect(trackers.get('impressions').has('imptracker.com')).to.be.true;
});
})
37 changes: 8 additions & 29 deletions test/spec/videoCache_spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import chai from 'chai';
import { registerVastTrackers } from 'src/video.js';
import { getCacheUrl, store } from 'src/videoCache.js';
import { config } from 'src/config.js';
import { server } from 'test/mocks/xhr.js';
import {auctionManager} from '../../src/auctionManager.js';
import {AuctionIndex} from '../../src/auctionIndex.js';
import { batchingCache } from '../../src/auction.js';
import { MODULE_TYPE_ANALYTICS } from 'src/activities/modules.js';
import {getCacheUrl, store} from 'src/videoCache.js';
import {config} from 'src/config.js';
import {server} from 'test/mocks/xhr.js';
import {auctionManager} from 'src/auctionManager.js';
import {AuctionIndex} from 'src/auctionIndex.js';
import {batchingCache} from 'src/auction.js';

const should = chai.should();

Expand Down Expand Up @@ -151,12 +149,7 @@ describe('The video cache', function () {
assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: 'imptracker.com', ttl: 25 }, expectedValue)
});

it('should include additional impressions trackers on top of vastImpUrl when they exist', function() {
registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) {
return [
{'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`}
];
});
it('should include multiple vastImpUrl when it\'s an array', function() {
const expectedValue = `<VAST version="3.0">
<Ad>
<Wrapper>
Expand All @@ -167,21 +160,7 @@ describe('The video cache', function () {
</Wrapper>
</Ad>
</VAST>`;
assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: 'imptracker.com', ttl: 25, cpm: 1.2 }, expectedValue)
});

it('should include only additional impressions trackers when they exist', function() {
const expectedValue = `<VAST version="3.0">
<Ad>
<Wrapper>
<AdSystem>prebid.org wrapper</AdSystem>
<VASTAdTagURI><![CDATA[my-mock-url.com]]></VASTAdTagURI>
<Impression><![CDATA[https://vasttracking.mydomain.com/vast?cpm=1.2]]></Impression>
<Creatives></Creatives>
</Wrapper>
</Ad>
</VAST>`;
assertRequestMade({ vastUrl: 'my-mock-url.com', ttl: 25, cpm: 1.2 }, expectedValue)
assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: ['https://vasttracking.mydomain.com/vast?cpm=1.2', 'imptracker.com'], ttl: 25, cpm: 1.2 }, expectedValue)
});

it('should make the expected request when store() is called on an ad with vastXml', function () {
Expand Down
29 changes: 0 additions & 29 deletions test/spec/video_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,33 +101,4 @@ describe('video.js', function () {
const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})});
expect(valid).to.equal(false);
});

it('insert into tracker list', function() {
let trackers = getVastTrackers({'cpm': 1.0});
if (!trackers || !trackers.get('impressions')) {
registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) {
return [
{'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`}
];
});
}
trackers = getVastTrackers({'cpm': 1.0});
expect(trackers).to.be.a('map');
expect(trackers.get('impressions')).to.exists;
expect(trackers.get('impressions').has('https://vasttracking.mydomain.com/vast?cpm=1')).to.be.true;
});

it('insert trackers in vastXml', function() {
const trackers = getVastTrackers({'cpm': 1.0});
let vastXml = '<VAST><Ad><Wrapper></Wrapper></Ad></VAST>';
vastXml = insertVastTrackers(trackers, vastXml);
expect(vastXml).to.equal('<VAST><Ad><Wrapper><Impression><![CDATA[https://vasttracking.mydomain.com/vast?cpm=1]]></Impression></Wrapper></Ad></VAST>');
});

it('test addImpUrlToTrackers', function() {
const trackers = addImpUrlToTrackers({'vastImpUrl': 'imptracker.com'}, getVastTrackers({'cpm': 1.0}));
expect(trackers).to.be.a('map');
expect(trackers.get('impressions')).to.exists;
expect(trackers.get('impressions').has('imptracker.com')).to.be.true;
});
});

0 comments on commit a09dd88

Please sign in to comment.