Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PBjs Core : add ability to inject tracking in video #10191

Merged
merged 22 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e407ca2
add vast impression tracking
matthieularere-msq Jul 7, 2023
14d1d3b
support additional context macro
matthieularere-msq Jul 7, 2023
643c74a
fix spaces and singlequotes
matthieularere-msq Jul 7, 2023
e70f141
remove 2494945CONTEXT2494945 macro
matthieularere-msq Jul 10, 2023
2019c74
remove CONTEXT macro
matthieularere-msq Jul 10, 2023
53f94b3
do not update vastImpUrl anymore
matthieularere-msq Jul 11, 2023
e182c18
add impression trackers in video cache
matthieularere-msq Jul 11, 2023
2587ba6
insert ony unique trackers
matthieularere-msq Jul 11, 2023
4f8086f
rename registerVastTrackers
matthieularere-msq Jul 11, 2023
94bbe1f
rename arrayVastTrackers
matthieularere-msq Jul 11, 2023
1934a1b
trackers object change
matthieularere-msq Jul 12, 2023
4a1eeaf
check modules are allowed to add trackers based on isActivityAllowed
matthieularere-msq Jul 12, 2023
1138443
rename validVastTracker and add line breaks
matthieularere-msq Jul 21, 2023
a7d72f5
removes duplicates verification in isValidVastTracker
matthieularere-msq Jul 21, 2023
2c18305
changes in wrapURI + typo fix
matthieularere-msq Jul 21, 2023
ecd61fe
requested changes
matthieularere-msq Aug 31, 2023
641c597
update function trackersToMap
matthieularere-msq Aug 31, 2023
cb593d5
using Set in trackers map
matthieularere-msq Aug 31, 2023
b74c5c6
changes suggested by dgirardi
matthieularere-msq Jan 19, 2024
8083d25
changes suggested by dgirardi
matthieularere-msq Jan 19, 2024
98f5d80
Update test/spec/video_spec.js
matthieularere-msq Jan 31, 2024
84d039c
add spaces
matthieularere-msq Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
7 changes: 4 additions & 3 deletions src/videoCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,18 @@ 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, impUrl) {
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 vastImp = (impUrl) ? `<![CDATA[${impUrl}]]>` : ``;
let impressions = impTrackerURLs ? impTrackerURLs.map(trk => `<Impression><![CDATA[${trk}]]></Impression>`).join('') : '';
return `<VAST version="3.0">
<Ad>
<Wrapper>
<AdSystem>prebid.org wrapper</AdSystem>
<VASTAdTagURI><![CDATA[${uri}]]></VASTAdTagURI>
<Impression>${vastImp}</Impression>
${impressions}
<Creatives></Creatives>
</Wrapper>
</Ad>
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;
});
})
24 changes: 19 additions & 5 deletions test/spec/videoCache_spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import chai from 'chai';
import { getCacheUrl, store } from 'src/videoCache.js';
import { config } from 'src/config.js';
import { server } from 'test/mocks/xhr.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 {batchingCache} from '../../src/auction.js';

const should = chai.should();

Expand Down Expand Up @@ -127,7 +127,7 @@ describe('The video cache', function () {
<Wrapper>
<AdSystem>prebid.org wrapper</AdSystem>
<VASTAdTagURI><![CDATA[my-mock-url.com]]></VASTAdTagURI>
<Impression></Impression>

<Creatives></Creatives>
</Wrapper>
</Ad>
Expand All @@ -149,6 +149,20 @@ describe('The video cache', function () {
assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: 'imptracker.com', ttl: 25 }, expectedValue)
});

it('should include multiple vastImpUrl when it\'s an array', 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><Impression><![CDATA[imptracker.com]]></Impression>
<Creatives></Creatives>
</Wrapper>
</Ad>
</VAST>`;
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 () {
const vastXml = '<VAST version="3.0"></VAST>';
assertRequestMade({ vastXml: vastXml, ttl: 25 }, vastXml);
Expand Down