From e407ca233491e5ee0942169e7a4d81bddb3dcd75 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 7 Jul 2023 18:41:04 +0200 Subject: [PATCH 01/22] add vast impression tracking --- src/auction.js | 4 +++- src/video.js | 48 +++++++++++++++++++++++++++++++++++++++++ test/spec/video_spec.js | 33 +++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/auction.js b/src/auction.js index 69febccc94e..f7df438c104 100644 --- a/src/auction.js +++ b/src/auction.js @@ -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} from './video.js'; +import {OUTSTREAM, getVastTrackers, insertVastTrackers} from './video.js'; import {VIDEO} from './mediaTypes.js'; import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; @@ -582,7 +582,9 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au }), 'video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); + const [hasTrackers, vastTrackers] = getVastTrackers(bidResponse); + if (hasTrackers) { [bidResponse.vastXml, bidResponse.vastImpUrl] = insertVastTrackers(vastTrackers, bidResponse.vastXml, bidResponse.vastImpUrl); } if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { addBid = false; diff --git a/src/video.js b/src/video.js index 7930e318874..79110b7cc10 100644 --- a/src/video.js +++ b/src/video.js @@ -21,6 +21,8 @@ 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 @@ -64,3 +66,49 @@ export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaT return true; }, 'checkVideoBidSetup'); + +export function registerVASTTrackers(tracker) { + vastTrackers.push(tracker); +} + +export function insertVastTrackers(trackers, vastXml, vastImpUrl) { + 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 => { + trackers['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'); + } + if (vastImpUrl && vastImpUrl.length > 0) { + trackers['impressions'].forEach(trackingUrl => { + if ((trackingUrl + encodeURI(vastImpUrl)).length < 2048) { + vastImpUrl = trackingUrl + encodeURI(vastImpUrl); + } + }); + }; + return [vastXml, vastImpUrl]; +} + +export function getVastTrackers(bid) { + let hasTrackers = false; + let trackers = {'impressions': []}; + vastTrackers.forEach(func => { + let tmpTrackers = func(bid); + for (const key in tmpTrackers) { + if (key in trackers && Array.isArray(tmpTrackers[key])) { + trackers[key] = trackers[key].concat(tmpTrackers[key]); + hasTrackers = true + } + } + }); + return [hasTrackers, trackers]; +} diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 61621c7ec42..92bd3865c6c 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,4 +1,4 @@ -import { isValidVideoBid } from 'src/video.js'; +import { isValidVideoBid, registerVASTTrackers, insertVastTrackers, getVastTrackers } from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; @@ -100,4 +100,35 @@ describe('video.js', function () { const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); + + it('insert into tracker list', function() { + registerVASTTrackers(function(bidResponse) { + return { + 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}&redir=`] + }; + }); + const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); + expect(hasTrackers).to.equal(true); + expect(trackers).to.have.property('impressions'); + expect(trackers.impressions.length).to.equal(1); + expect(trackers.impressions[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1&redir='); + }); + + it('insert trackers in vastXml', function() { + const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); + let vastXml = ''; + let vastImpUrl; + [vastXml, vastImpUrl] = insertVastTrackers(trackers, vastXml, vastImpUrl); + expect(vastImpUrl).to.equal(undefined); + expect(vastXml).to.equal(''); + }); + + it('insert trackers in vastImpUrl', function() { + const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); + let vastXml; + let vastImpUrl = 'https://finalvast.domain.com'; + [vastXml, vastImpUrl] = insertVastTrackers(trackers, vastXml, vastImpUrl); + expect(vastXml).to.equal(undefined); + expect(vastImpUrl).to.equal('https://vasttracking.mydomain.com/vast?cpm=1&redir=' + encodeURI('https://finalvast.domain.com')); + }); }); From 14d1d3bd8ba5b8529a8c1b8ae00138d5f718155e Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 7 Jul 2023 19:31:31 +0200 Subject: [PATCH 02/22] support additional context macro --- src/video.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/video.js b/src/video.js index 79110b7cc10..5cfac7f1798 100644 --- a/src/video.js +++ b/src/video.js @@ -99,13 +99,16 @@ export function insertVastTrackers(trackers, vastXml, vastImpUrl) { } export function getVastTrackers(bid) { + const videoMediaType = deepAccess(auctionManager.index.getMediaTypes(bid), 'video'); + const context = videoMediaType && deepAccess(videoMediaType, 'context'); let hasTrackers = false; let trackers = {'impressions': []}; vastTrackers.forEach(func => { let tmpTrackers = func(bid); for (const key in tmpTrackers) { if (key in trackers && Array.isArray(tmpTrackers[key])) { - trackers[key] = trackers[key].concat(tmpTrackers[key]); + let replaceContext = tmpTrackers[key].map(function(url) {return url.replace("$$CONTEXT$$", context);}); + trackers[key] = trackers[key].concat(replaceContext); hasTrackers = true } } From 643c74a07d8c631df64d7dfff20b79e060200644 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 7 Jul 2023 22:18:58 +0200 Subject: [PATCH 03/22] fix spaces and singlequotes --- src/video.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video.js b/src/video.js index 5cfac7f1798..104a342dcd0 100644 --- a/src/video.js +++ b/src/video.js @@ -107,7 +107,7 @@ export function getVastTrackers(bid) { let tmpTrackers = func(bid); for (const key in tmpTrackers) { if (key in trackers && Array.isArray(tmpTrackers[key])) { - let replaceContext = tmpTrackers[key].map(function(url) {return url.replace("$$CONTEXT$$", context);}); + let replaceContext = tmpTrackers[key].map(function(url) { return url.replace('$$CONTEXT$$', context); }); trackers[key] = trackers[key].concat(replaceContext); hasTrackers = true } From e70f141111574becca553295e26ac0a99c6bd664 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Mon, 10 Jul 2023 21:07:26 +0200 Subject: [PATCH 04/22] remove 2494945CONTEXT2494945 macro --- src/video.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/video.js b/src/video.js index 104a342dcd0..afdbb80fcec 100644 --- a/src/video.js +++ b/src/video.js @@ -107,8 +107,7 @@ export function getVastTrackers(bid) { let tmpTrackers = func(bid); for (const key in tmpTrackers) { if (key in trackers && Array.isArray(tmpTrackers[key])) { - let replaceContext = tmpTrackers[key].map(function(url) { return url.replace('$$CONTEXT$$', context); }); - trackers[key] = trackers[key].concat(replaceContext); + trackers[key] = trackers[key].concat(tmpTrackers[key]); hasTrackers = true } } From 2019c74046d10aeda863b0042dd6948692d5978a Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Mon, 10 Jul 2023 21:11:35 +0200 Subject: [PATCH 05/22] remove CONTEXT macro --- src/video.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/video.js b/src/video.js index afdbb80fcec..79110b7cc10 100644 --- a/src/video.js +++ b/src/video.js @@ -99,8 +99,6 @@ export function insertVastTrackers(trackers, vastXml, vastImpUrl) { } export function getVastTrackers(bid) { - const videoMediaType = deepAccess(auctionManager.index.getMediaTypes(bid), 'video'); - const context = videoMediaType && deepAccess(videoMediaType, 'context'); let hasTrackers = false; let trackers = {'impressions': []}; vastTrackers.forEach(func => { From 53f94b32a731afe266c2d3a9d102febdacb2a4c6 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Tue, 11 Jul 2023 10:34:48 +0200 Subject: [PATCH 06/22] do not update vastImpUrl anymore --- src/auction.js | 2 +- src/video.js | 11 ++--------- test/spec/video_spec.js | 19 ++++--------------- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/auction.js b/src/auction.js index f7df438c104..d0c0f184fc4 100644 --- a/src/auction.js +++ b/src/auction.js @@ -584,7 +584,7 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); const [hasTrackers, vastTrackers] = getVastTrackers(bidResponse); - if (hasTrackers) { [bidResponse.vastXml, bidResponse.vastImpUrl] = insertVastTrackers(vastTrackers, bidResponse.vastXml, bidResponse.vastImpUrl); } + if (hasTrackers) { bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml); } if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { addBid = false; diff --git a/src/video.js b/src/video.js index 79110b7cc10..468bc90d500 100644 --- a/src/video.js +++ b/src/video.js @@ -71,7 +71,7 @@ export function registerVASTTrackers(tracker) { vastTrackers.push(tracker); } -export function insertVastTrackers(trackers, vastXml, vastImpUrl) { +export function insertVastTrackers(trackers, vastXml) { const doc = new DOMParser().parseFromString(vastXml, 'text/xml'); const wrappers = doc.querySelectorAll('VAST Ad Wrapper, VAST Ad InLine'); try { @@ -88,14 +88,7 @@ export function insertVastTrackers(trackers, vastXml, vastImpUrl) { } catch (error) { logError('an error happened trying to insert trackers in vastXml'); } - if (vastImpUrl && vastImpUrl.length > 0) { - trackers['impressions'].forEach(trackingUrl => { - if ((trackingUrl + encodeURI(vastImpUrl)).length < 2048) { - vastImpUrl = trackingUrl + encodeURI(vastImpUrl); - } - }); - }; - return [vastXml, vastImpUrl]; + return vastXml; } export function getVastTrackers(bid) { diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 92bd3865c6c..2a13d250d0c 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -104,31 +104,20 @@ describe('video.js', function () { it('insert into tracker list', function() { registerVASTTrackers(function(bidResponse) { return { - 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}&redir=`] + 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`] }; }); const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); expect(hasTrackers).to.equal(true); expect(trackers).to.have.property('impressions'); expect(trackers.impressions.length).to.equal(1); - expect(trackers.impressions[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1&redir='); + expect(trackers.impressions[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1'); }); it('insert trackers in vastXml', function() { const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); let vastXml = ''; - let vastImpUrl; - [vastXml, vastImpUrl] = insertVastTrackers(trackers, vastXml, vastImpUrl); - expect(vastImpUrl).to.equal(undefined); - expect(vastXml).to.equal(''); - }); - - it('insert trackers in vastImpUrl', function() { - const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); - let vastXml; - let vastImpUrl = 'https://finalvast.domain.com'; - [vastXml, vastImpUrl] = insertVastTrackers(trackers, vastXml, vastImpUrl); - expect(vastXml).to.equal(undefined); - expect(vastImpUrl).to.equal('https://vasttracking.mydomain.com/vast?cpm=1&redir=' + encodeURI('https://finalvast.domain.com')); + vastXml = insertVastTrackers(trackers, vastXml); + expect(vastXml).to.equal(''); }); }); From e182c18c40c34ffb7829fcecaa03945bbad4a2a3 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Tue, 11 Jul 2023 10:35:22 +0200 Subject: [PATCH 07/22] add impression trackers in video cache --- src/videoCache.js | 14 ++++++++++---- test/spec/videoCache_spec.js | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/videoCache.js b/src/videoCache.js index 88fc27625fd..a5ffe98bc2b 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -12,6 +12,7 @@ import {ajaxBuilder} from './ajax.js'; import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; +import {getVastTrackers} from './video.js'; /** * Might be useful to be configurable in the future @@ -42,17 +43,22 @@ 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, impUrl, arrayVastTrackers) { // 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) ? `` : ``; + let impressions = (impUrl) ? `` : ``; + if (Array.isArray(arrayVastTrackers) && arrayVastTrackers.length == 2 && arrayVastTrackers[1].hasOwnProperty('impressions')) { + arrayVastTrackers[1]['impressions'].forEach(trackingImp => { + impressions += ``; + }); + } return ` prebid.org wrapper - ${vastImp} + ${impressions} @@ -67,7 +73,7 @@ function wrapURI(uri, impUrl) { * @param index */ function toStorageRequest(bid, {index = auctionManager.index} = {}) { - const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); + const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl, getVastTrackers(bid)); const auction = index.getAuction(bid); const ttlWithBuffer = Number(bid.ttl) + ttlBufferInSeconds; let payload = { diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index c7c0b2eb329..bd92c60ec42 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,4 +1,5 @@ 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'; @@ -127,7 +128,7 @@ describe('The video cache', function () { prebid.org wrapper - + @@ -149,6 +150,39 @@ 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(function(bidResponse) { + return { + 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`] + }; + }); + const expectedValue = ` + + + prebid.org wrapper + + + + + + `; + 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 = ` + + + prebid.org wrapper + + + + + + `; + assertRequestMade({ vastUrl: 'my-mock-url.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 = ''; assertRequestMade({ vastXml: vastXml, ttl: 25 }, vastXml); From 2587ba63d1fb5eaed0b97a229f0c6ee40547dee6 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Tue, 11 Jul 2023 11:57:51 +0200 Subject: [PATCH 08/22] insert ony unique trackers --- src/video.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/video.js b/src/video.js index 468bc90d500..182a5cee768 100644 --- a/src/video.js +++ b/src/video.js @@ -98,8 +98,13 @@ export function getVastTrackers(bid) { let tmpTrackers = func(bid); for (const key in tmpTrackers) { if (key in trackers && Array.isArray(tmpTrackers[key])) { - trackers[key] = trackers[key].concat(tmpTrackers[key]); - hasTrackers = true + // only add not existing trackers + tmpTrackers[key].forEach(item => { + if (!trackers[key].includes(item)) { + trackers[key].push(item); + hasTrackers = true; + } + }); } } }); From 4f8086f133c0c756cb9be9a13662f05d363bf84a Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Tue, 11 Jul 2023 23:06:38 +0200 Subject: [PATCH 09/22] rename registerVastTrackers --- src/video.js | 2 +- test/spec/videoCache_spec.js | 4 ++-- test/spec/video_spec.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/video.js b/src/video.js index 182a5cee768..dadad39dbb4 100644 --- a/src/video.js +++ b/src/video.js @@ -67,7 +67,7 @@ export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaT return true; }, 'checkVideoBidSetup'); -export function registerVASTTrackers(tracker) { +export function registerVastTrackers(tracker) { vastTrackers.push(tracker); } diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index bd92c60ec42..94cc312b63b 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,5 +1,5 @@ import chai from 'chai'; -import { registerVASTTrackers } from 'src/video.js'; +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'; @@ -151,7 +151,7 @@ describe('The video cache', function () { }); it('should include additional impressions trackers on top of vastImpUrl when they exist', function() { - registerVASTTrackers(function(bidResponse) { + registerVastTrackers(function(bidResponse) { return { 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`] }; diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 2a13d250d0c..b20bca5f13e 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,4 +1,4 @@ -import { isValidVideoBid, registerVASTTrackers, insertVastTrackers, getVastTrackers } from 'src/video.js'; +import { isValidVideoBid, registerVastTrackers, insertVastTrackers, getVastTrackers } from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; @@ -102,7 +102,7 @@ describe('video.js', function () { }); it('insert into tracker list', function() { - registerVASTTrackers(function(bidResponse) { + registerVastTrackers(function(bidResponse) { return { 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`] }; From 94bbe1fea5e424dd32e259d1aac9cc39f0770d84 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Tue, 11 Jul 2023 23:08:02 +0200 Subject: [PATCH 10/22] rename arrayVastTrackers --- src/videoCache.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/videoCache.js b/src/videoCache.js index a5ffe98bc2b..291c0f72a97 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -43,13 +43,13 @@ 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, arrayVastTrackers) { +function wrapURI(uri, impUrl, vastTrackers) { // 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 = (impUrl) ? `` : ``; - if (Array.isArray(arrayVastTrackers) && arrayVastTrackers.length == 2 && arrayVastTrackers[1].hasOwnProperty('impressions')) { - arrayVastTrackers[1]['impressions'].forEach(trackingImp => { + if (Array.isArray(vastTrackers) && vastTrackers.length == 2 && vastTrackers[1].hasOwnProperty('impressions')) { + vastTrackers[1]['impressions'].forEach(trackingImp => { impressions += ``; }); } From 1934a1b4cff685b1ca8346d024fa1ada87d22752 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Wed, 12 Jul 2023 12:14:28 +0200 Subject: [PATCH 11/22] trackers object change --- src/auction.js | 4 +-- src/video.js | 51 ++++++++++++++++++++++-------------- src/videoCache.js | 4 +-- test/spec/videoCache_spec.js | 6 ++--- test/spec/video_spec.js | 18 ++++++------- 5 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/auction.js b/src/auction.js index d0c0f184fc4..43b80ee0839 100644 --- a/src/auction.js +++ b/src/auction.js @@ -582,9 +582,9 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au }), 'video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); - const [hasTrackers, vastTrackers] = getVastTrackers(bidResponse); + const vastTrackers = getVastTrackers(bidResponse); - if (hasTrackers) { bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml); } + 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; diff --git a/src/video.js b/src/video.js index dadad39dbb4..b84215fc056 100644 --- a/src/video.js +++ b/src/video.js @@ -77,11 +77,13 @@ export function insertVastTrackers(trackers, vastXml) { try { if (wrappers.length) { wrappers.forEach(wrapper => { - trackers['impressions'].forEach(trackingUrl => { - const impression = doc.createElement('Impression'); - impression.appendChild(doc.createCDATASection(trackingUrl)); - wrapper.appendChild(impression) - }); + if (trackers.get('impression')) { + trackers.get('impression').forEach(trackingUrl => { + const impression = doc.createElement('Impression'); + impression.appendChild(doc.createCDATASection(trackingUrl)); + wrapper.appendChild(impression) + }); + } }); vastXml = new XMLSerializer().serializeToString(doc); } @@ -92,21 +94,30 @@ export function insertVastTrackers(trackers, vastXml) { } export function getVastTrackers(bid) { - let hasTrackers = false; - let trackers = {'impressions': []}; + let trackers = []; vastTrackers.forEach(func => { - let tmpTrackers = func(bid); - for (const key in tmpTrackers) { - if (key in trackers && Array.isArray(tmpTrackers[key])) { - // only add not existing trackers - tmpTrackers[key].forEach(item => { - if (!trackers[key].includes(item)) { - trackers[key].push(item); - hasTrackers = true; - } - }); - } - } + // get tracker list from function + let trackersToAdd = func(bid); + trackersToAdd.forEach(trackerToAdd => { + if (validVastTracker(trackers, trackerToAdd)) { trackers.push(trackerToAdd); } + }); + }); + const trackersMap = trackersToMap(trackers); + return (trackersMap.size ? trackersMap : null); +}; + +function validVastTracker(trackers, trackerToAdd) { + if (!trackerToAdd.hasOwnProperty('event') || !trackerToAdd.hasOwnProperty('url')) { return false; } + trackers.forEach(tracker => { + if (tracker['event'] == trackerToAdd['event'] && tracker['url'] == trackerToAdd['url']) { return false; } + }); + return true; +} + +function trackersToMap(trackers) { + let trackersMap = new Map(); + trackers.forEach(tracker => { + if (!trackersMap.get(tracker['event'])) { trackersMap.set(tracker['event'], [tracker['url']]) } else { trackersMap.get(tracker['event']).push(tracker['url']); } }); - return [hasTrackers, trackers]; + return trackersMap; } diff --git a/src/videoCache.js b/src/videoCache.js index 291c0f72a97..5849b0e9ce9 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -48,8 +48,8 @@ function wrapURI(uri, impUrl, vastTrackers) { // 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 = (impUrl) ? `` : ``; - if (Array.isArray(vastTrackers) && vastTrackers.length == 2 && vastTrackers[1].hasOwnProperty('impressions')) { - vastTrackers[1]['impressions'].forEach(trackingImp => { + if (vastTrackers && vastTrackers.get('impressions')) { + vastTrackers.get('impressions').forEach(trackingImp => { impressions += ``; }); } diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 94cc312b63b..0477c93a8db 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -152,9 +152,9 @@ describe('The video cache', function () { it('should include additional impressions trackers on top of vastImpUrl when they exist', function() { registerVastTrackers(function(bidResponse) { - return { - 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`] - }; + return [ + {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} + ]; }); const expectedValue = ` diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index b20bca5f13e..bb082e55156 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -103,19 +103,19 @@ describe('video.js', function () { it('insert into tracker list', function() { registerVastTrackers(function(bidResponse) { - return { - 'impressions': [`https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`] - }; + return [ + {'event': 'impression', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} + ]; }); - const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); - expect(hasTrackers).to.equal(true); - expect(trackers).to.have.property('impressions'); - expect(trackers.impressions.length).to.equal(1); - expect(trackers.impressions[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1'); + const trackers = getVastTrackers({'cpm': 1.0}); + expect(trackers).to.be.a('map'); + expect(trackers.get('impression')).to.exists; + expect(trackers.get('impression').length).to.equal(1); + expect(trackers.get('impression')[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1'); }); it('insert trackers in vastXml', function() { - const [hasTrackers, trackers] = getVastTrackers({'cpm': 1.0}); + const trackers = getVastTrackers({'cpm': 1.0}); let vastXml = ''; vastXml = insertVastTrackers(trackers, vastXml); expect(vastXml).to.equal(''); From 4a1eeaf8c16fad8d61dacfd8ae8c9a805da56767 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Wed, 12 Jul 2023 17:27:13 +0200 Subject: [PATCH 12/22] check modules are allowed to add trackers based on isActivityAllowed --- src/video.js | 14 +++++++++----- test/spec/videoCache_spec.js | 3 ++- test/spec/video_spec.js | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/video.js b/src/video.js index b84215fc056..0423f4968d7 100644 --- a/src/video.js +++ b/src/video.js @@ -4,6 +4,9 @@ import { config } from '../src/config.js'; import {includes} from './polyfill.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'; @@ -67,8 +70,8 @@ export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaT return true; }, 'checkVideoBidSetup'); -export function registerVastTrackers(tracker) { - vastTrackers.push(tracker); +export function registerVastTrackers(moduleType, moduleName, trackerFn) { + if (typeof trackerFn == 'function') { vastTrackers.push({'moduleType': moduleType, 'moduleName': moduleName, 'trackerFn': trackerFn}); } } export function insertVastTrackers(trackers, vastXml) { @@ -95,9 +98,10 @@ export function insertVastTrackers(trackers, vastXml) { export function getVastTrackers(bid) { let trackers = []; - vastTrackers.forEach(func => { - // get tracker list from function - let trackersToAdd = func(bid); + vastTrackers.filter( + ({moduleType, moduleName, trackerFn}) => isActivityAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(moduleType, moduleName)) + ).forEach(({trackerFn}) => { + let trackersToAdd = trackerFn(bid); trackersToAdd.forEach(trackerToAdd => { if (validVastTracker(trackers, trackerToAdd)) { trackers.push(trackerToAdd); } }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 0477c93a8db..4dd8480dd01 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -6,6 +6,7 @@ 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'; const should = chai.should(); @@ -151,7 +152,7 @@ describe('The video cache', function () { }); it('should include additional impressions trackers on top of vastImpUrl when they exist', function() { - registerVastTrackers(function(bidResponse) { + registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { return [ {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} ]; diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index bb082e55156..9596b94b579 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,6 +1,7 @@ import { isValidVideoBid, registerVastTrackers, insertVastTrackers, getVastTrackers } from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; +import { MODULE_TYPE_ANALYTICS } from 'src/activities/modules.js'; describe('video.js', function () { before(() => { @@ -102,7 +103,7 @@ describe('video.js', function () { }); it('insert into tracker list', function() { - registerVastTrackers(function(bidResponse) { + registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { return [ {'event': 'impression', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} ]; From 113844302857f0869aa3cc3cdd4f271e2cada012 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 21 Jul 2023 14:56:47 +0200 Subject: [PATCH 13/22] rename validVastTracker and add line breaks --- src/video.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/video.js b/src/video.js index 0423f4968d7..09949947f5f 100644 --- a/src/video.js +++ b/src/video.js @@ -103,14 +103,14 @@ export function getVastTrackers(bid) { ).forEach(({trackerFn}) => { let trackersToAdd = trackerFn(bid); trackersToAdd.forEach(trackerToAdd => { - if (validVastTracker(trackers, trackerToAdd)) { trackers.push(trackerToAdd); } + if (isValidVastTracker(trackers, trackerToAdd)) { trackers.push(trackerToAdd); } }); }); const trackersMap = trackersToMap(trackers); return (trackersMap.size ? trackersMap : null); }; -function validVastTracker(trackers, trackerToAdd) { +function isValidVastTracker(trackers, trackerToAdd) { if (!trackerToAdd.hasOwnProperty('event') || !trackerToAdd.hasOwnProperty('url')) { return false; } trackers.forEach(tracker => { if (tracker['event'] == trackerToAdd['event'] && tracker['url'] == trackerToAdd['url']) { return false; } @@ -121,7 +121,11 @@ function validVastTracker(trackers, trackerToAdd) { function trackersToMap(trackers) { let trackersMap = new Map(); trackers.forEach(tracker => { - if (!trackersMap.get(tracker['event'])) { trackersMap.set(tracker['event'], [tracker['url']]) } else { trackersMap.get(tracker['event']).push(tracker['url']); } + if (!trackersMap.get(tracker['event'])) { + trackersMap.set(tracker['event'], [tracker['url']]) + } else { + trackersMap.get(tracker['event']).push(tracker['url']); + } }); return trackersMap; } From a7d72f5bdcaacd951f64520482b5b3f04c4ca3c7 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 21 Jul 2023 15:34:08 +0200 Subject: [PATCH 14/22] removes duplicates verification in isValidVastTracker --- src/video.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/video.js b/src/video.js index 09949947f5f..39161443ad6 100644 --- a/src/video.js +++ b/src/video.js @@ -112,9 +112,6 @@ export function getVastTrackers(bid) { function isValidVastTracker(trackers, trackerToAdd) { if (!trackerToAdd.hasOwnProperty('event') || !trackerToAdd.hasOwnProperty('url')) { return false; } - trackers.forEach(tracker => { - if (tracker['event'] == trackerToAdd['event'] && tracker['url'] == trackerToAdd['url']) { return false; } - }); return true; } From 2c1830599b84557b1ad12f1d90b52f3781c3cf07 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 21 Jul 2023 17:48:09 +0200 Subject: [PATCH 15/22] changes in wrapURI + typo fix --- src/video.js | 22 +++++++++++++++++++--- src/videoCache.js | 8 ++++---- test/spec/videoCache_spec.js | 2 +- test/spec/video_spec.js | 18 +++++++++++++----- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/video.js b/src/video.js index 39161443ad6..e48d15e0eba 100644 --- a/src/video.js +++ b/src/video.js @@ -80,8 +80,8 @@ export function insertVastTrackers(trackers, vastXml) { try { if (wrappers.length) { wrappers.forEach(wrapper => { - if (trackers.get('impression')) { - trackers.get('impression').forEach(trackingUrl => { + if (trackers.get('impressions')) { + trackers.get('impressions').forEach(trackingUrl => { const impression = doc.createElement('Impression'); impression.appendChild(doc.createCDATASection(trackingUrl)); wrapper.appendChild(impression) @@ -115,14 +115,30 @@ function isValidVastTracker(trackers, trackerToAdd) { return true; } +// need to add this othersize gulp test for test/spec/video_spec.js and test/spec/videoCache_spec.js duplicates their trackings +function isTrackingDuplicate(trackersMap, trackingEvent, trackingUrl) { + const urls = trackersMap.get(trackingEvent); + if (Array.isArray(urls) && urls.includes(trackingUrl)) { return true; } + return false; +} + function trackersToMap(trackers) { let trackersMap = new Map(); trackers.forEach(tracker => { if (!trackersMap.get(tracker['event'])) { trackersMap.set(tracker['event'], [tracker['url']]) - } else { + } else if (!isTrackingDuplicate(trackersMap, tracker['event'], tracker['url'])) { trackersMap.get(tracker['event']).push(tracker['url']); } }); return trackersMap; } + +export function addImpUrlToTrackers(bid, trackersMap) { + if (bid.vastImpUrl) { + if (!trackersMap) { trackersMap = new Map(); } + if (!trackersMap.get('impressions')) { trackersMap.set('impressions', []); } + trackersMap.get('impressions').push(bid.vastImpUrl); + } + return trackersMap; +} diff --git a/src/videoCache.js b/src/videoCache.js index 5849b0e9ce9..50a72565795 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -12,7 +12,7 @@ import {ajaxBuilder} from './ajax.js'; import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; -import {getVastTrackers} from './video.js'; +import {getVastTrackers, addImpUrlToTrackers} from './video.js'; /** * Might be useful to be configurable in the future @@ -43,11 +43,11 @@ 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, vastTrackers) { +function wrapURI(uri, vastTrackers) { // 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 = (impUrl) ? `` : ``; + let impressions = ''; if (vastTrackers && vastTrackers.get('impressions')) { vastTrackers.get('impressions').forEach(trackingImp => { impressions += ``; @@ -73,7 +73,7 @@ function wrapURI(uri, impUrl, vastTrackers) { * @param index */ function toStorageRequest(bid, {index = auctionManager.index} = {}) { - const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl, getVastTrackers(bid)); + const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, addImpUrlToTrackers(bid, getVastTrackers(bid))); const auction = index.getAuction(bid); const ttlWithBuffer = Number(bid.ttl) + ttlBufferInSeconds; let payload = { diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 4dd8480dd01..8f37dc11fc3 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -162,7 +162,7 @@ describe('The video cache', function () { prebid.org wrapper - + diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 9596b94b579..238862358fd 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,4 +1,4 @@ -import { isValidVideoBid, registerVastTrackers, insertVastTrackers, getVastTrackers } from 'src/video.js'; +import { isValidVideoBid, registerVastTrackers, insertVastTrackers, getVastTrackers, addImpUrlToTrackers } from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; import { MODULE_TYPE_ANALYTICS } from 'src/activities/modules.js'; @@ -105,14 +105,14 @@ describe('video.js', function () { it('insert into tracker list', function() { registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { return [ - {'event': 'impression', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} + {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} ]; }); const trackers = getVastTrackers({'cpm': 1.0}); expect(trackers).to.be.a('map'); - expect(trackers.get('impression')).to.exists; - expect(trackers.get('impression').length).to.equal(1); - expect(trackers.get('impression')[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1'); + expect(trackers.get('impressions')).to.exists; + expect(trackers.get('impressions').length).to.equal(1); + expect(trackers.get('impressions')[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1'); }); it('insert trackers in vastXml', function() { @@ -121,4 +121,12 @@ describe('video.js', function () { vastXml = insertVastTrackers(trackers, vastXml); expect(vastXml).to.equal(''); }); + + 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').length).to.equal(2); + expect(trackers.get('impressions')[1]).to.equal('imptracker.com'); + }); }); From ecd61fe552322a9f29f4f02e56c1869157435aa7 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Thu, 31 Aug 2023 14:48:48 +0200 Subject: [PATCH 16/22] requested changes --- src/auction.js | 4 +++- src/video.js | 16 +++++----------- test/spec/video_spec.js | 17 +++++++++-------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/auction.js b/src/auction.js index 43b80ee0839..25e06a494bc 100644 --- a/src/auction.js +++ b/src/auction.js @@ -584,7 +584,9 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); const vastTrackers = getVastTrackers(bidResponse); - if (vastTrackers) { bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml); } + 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; diff --git a/src/video.js b/src/video.js index e48d15e0eba..9ad92f25dc3 100644 --- a/src/video.js +++ b/src/video.js @@ -71,7 +71,9 @@ export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaT }, 'checkVideoBidSetup'); export function registerVastTrackers(moduleType, moduleName, trackerFn) { - if (typeof trackerFn == 'function') { vastTrackers.push({'moduleType': moduleType, 'moduleName': moduleName, 'trackerFn': trackerFn}); } + if (typeof trackerFn === 'function') { + vastTrackers.push({'moduleType': moduleType, 'moduleName': moduleName, 'trackerFn': trackerFn}); + } } export function insertVastTrackers(trackers, vastXml) { @@ -111,15 +113,7 @@ export function getVastTrackers(bid) { }; function isValidVastTracker(trackers, trackerToAdd) { - if (!trackerToAdd.hasOwnProperty('event') || !trackerToAdd.hasOwnProperty('url')) { return false; } - return true; -} - -// need to add this othersize gulp test for test/spec/video_spec.js and test/spec/videoCache_spec.js duplicates their trackings -function isTrackingDuplicate(trackersMap, trackingEvent, trackingUrl) { - const urls = trackersMap.get(trackingEvent); - if (Array.isArray(urls) && urls.includes(trackingUrl)) { return true; } - return false; + return trackerToAdd.hasOwnProperty('event') && trackerToAdd.hasOwnProperty('url'); } function trackersToMap(trackers) { @@ -127,7 +121,7 @@ function trackersToMap(trackers) { trackers.forEach(tracker => { if (!trackersMap.get(tracker['event'])) { trackersMap.set(tracker['event'], [tracker['url']]) - } else if (!isTrackingDuplicate(trackersMap, tracker['event'], tracker['url'])) { + } else { trackersMap.get(tracker['event']).push(tracker['url']); } }); diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 238862358fd..8e3db27d81f 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -103,15 +103,17 @@ describe('video.js', function () { }); it('insert into tracker list', function() { - registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { - return [ - {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} - ]; - }); - const trackers = getVastTrackers({'cpm': 1.0}); + 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').length).to.equal(1); expect(trackers.get('impressions')[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1'); }); @@ -126,7 +128,6 @@ describe('video.js', 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').length).to.equal(2); expect(trackers.get('impressions')[1]).to.equal('imptracker.com'); }); }); From 641c5978ee2385d848f744e3a75c60b86837ac2e Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Thu, 31 Aug 2023 15:21:10 +0200 Subject: [PATCH 17/22] update function trackersToMap --- src/video.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/video.js b/src/video.js index 9ad92f25dc3..13f036dd1bd 100644 --- a/src/video.js +++ b/src/video.js @@ -117,15 +117,11 @@ function isValidVastTracker(trackers, trackerToAdd) { } function trackersToMap(trackers) { - let trackersMap = new Map(); - trackers.forEach(tracker => { - if (!trackersMap.get(tracker['event'])) { - trackersMap.set(tracker['event'], [tracker['url']]) - } else { - trackersMap.get(tracker['event']).push(tracker['url']); - } - }); - return trackersMap; + return trackers.reduce((map, {url, event}) => { + !map.has(event) && map.set(event, []) + map.get(event).push(url); + return map; + }, new Map()) } export function addImpUrlToTrackers(bid, trackersMap) { From cb593d50b326621b9e5d67a22d5774931dfbbe86 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Thu, 31 Aug 2023 15:43:42 +0200 Subject: [PATCH 18/22] using Set in trackers map --- src/video.js | 8 ++++---- test/spec/video_spec.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/video.js b/src/video.js index 13f036dd1bd..303e15b5a00 100644 --- a/src/video.js +++ b/src/video.js @@ -118,8 +118,8 @@ function isValidVastTracker(trackers, trackerToAdd) { function trackersToMap(trackers) { return trackers.reduce((map, {url, event}) => { - !map.has(event) && map.set(event, []) - map.get(event).push(url); + !map.has(event) && map.set(event, new Set()) + map.get(event).add(url); return map; }, new Map()) } @@ -127,8 +127,8 @@ function trackersToMap(trackers) { export function addImpUrlToTrackers(bid, trackersMap) { if (bid.vastImpUrl) { if (!trackersMap) { trackersMap = new Map(); } - if (!trackersMap.get('impressions')) { trackersMap.set('impressions', []); } - trackersMap.get('impressions').push(bid.vastImpUrl); + if (!trackersMap.get('impressions')) { trackersMap.set('impressions', new Set()); } + trackersMap.get('impressions').add(bid.vastImpUrl); } return trackersMap; } diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 8e3db27d81f..eca668b71f5 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -114,7 +114,7 @@ describe('video.js', function () { trackers = getVastTrackers({'cpm': 1.0}); expect(trackers).to.be.a('map'); expect(trackers.get('impressions')).to.exists; - expect(trackers.get('impressions')[0]).to.equal('https://vasttracking.mydomain.com/vast?cpm=1'); + expect(trackers.get('impressions').has('https://vasttracking.mydomain.com/vast?cpm=1')).to.be.true; }); it('insert trackers in vastXml', function() { @@ -128,6 +128,6 @@ describe('video.js', 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')[1]).to.equal('imptracker.com'); + expect(trackers.get('impressions').has('imptracker.com')).to.be.true; }); }); From b74c5c6bcbd1dfdcbf57d4150670774eb739d9c5 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 19 Jan 2024 09:48:07 +0100 Subject: [PATCH 19/22] changes suggested by dgirardi --- src/auction.js | 6 +-- src/video.js | 74 ++---------------------------------- src/videoCache.js | 13 ++----- test/spec/videoCache_spec.js | 33 +++------------- test/spec/video_spec.js | 29 -------------- 5 files changed, 14 insertions(+), 141 deletions(-) diff --git a/src/auction.js b/src/auction.js index 25e06a494bc..69febccc94e 100644 --- a/src/auction.js +++ b/src/auction.js @@ -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'; @@ -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; diff --git a/src/video.js b/src/video.js index 303e15b5a00..6cdfa725471 100644 --- a/src/video.js +++ b/src/video.js @@ -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'; @@ -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 @@ -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; -} diff --git a/src/videoCache.js b/src/videoCache.js index 50a72565795..ce03f2f624e 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -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 @@ -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 += ``; - }); - } + let impressions = impTrackerURLs ? impTrackerURLs.map(trk => ``).join('') : ''; return ` @@ -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 = { diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 8f37dc11fc3..d2b732cfd5a 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -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 {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 {batchingCache} from '../../src/auction.js'; const should = chai.should(); @@ -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 = ` @@ -167,21 +160,7 @@ describe('The video cache', function () { `; - 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 = ` - - - prebid.org wrapper - - - - - - `; - 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 () { diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index eca668b71f5..08ce41440b0 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -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 = ''; - vastXml = insertVastTrackers(trackers, vastXml); - expect(vastXml).to.equal(''); - }); - - 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; - }); }); From 8083d25708875dfad623f1b58bdbc8e6db831295 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Fri, 19 Jan 2024 09:48:40 +0100 Subject: [PATCH 20/22] changes suggested by dgirardi --- libraries/vastTrackers/vastTrackers.js | 95 ++++++++++++++++++++++++ test/spec/libraries/vastTrackers_spec.js | 33 ++++++++ 2 files changed, 128 insertions(+) create mode 100644 libraries/vastTrackers/vastTrackers.js create mode 100644 test/spec/libraries/vastTrackers_spec.js diff --git a/libraries/vastTrackers/vastTrackers.js b/libraries/vastTrackers/vastTrackers.js new file mode 100644 index 00000000000..b4ae98aba57 --- /dev/null +++ b/libraries/vastTrackers/vastTrackers.js @@ -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; +} diff --git a/test/spec/libraries/vastTrackers_spec.js b/test/spec/libraries/vastTrackers_spec.js new file mode 100644 index 00000000000..3849ea75b02 --- /dev/null +++ b/test/spec/libraries/vastTrackers_spec.js @@ -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 = ''; + vastXml = insertVastTrackers(trackers, vastXml); + expect(vastXml).to.equal(''); + }); + + 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; + }); +}) From 98f5d803f86c888e7363c73c78971578e314f908 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:18:44 +0100 Subject: [PATCH 21/22] Update test/spec/video_spec.js Co-authored-by: Karim Mourra --- test/spec/video_spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 08ce41440b0..61621c7ec42 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,7 +1,6 @@ -import { isValidVideoBid, registerVastTrackers, insertVastTrackers, getVastTrackers, addImpUrlToTrackers } from 'src/video.js'; +import { isValidVideoBid } from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; -import { MODULE_TYPE_ANALYTICS } from 'src/activities/modules.js'; describe('video.js', function () { before(() => { From 84d039c8f56f92408e56cc60d5e3ab5227524137 Mon Sep 17 00:00:00 2001 From: matthieularere-msq Date: Wed, 31 Jan 2024 09:26:51 +0100 Subject: [PATCH 22/22] add spaces --- src/video.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/video.js b/src/video.js index 6cdfa725471..7930e318874 100644 --- a/src/video.js +++ b/src/video.js @@ -1,8 +1,8 @@ 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'; const VIDEO_MEDIA_TYPE = 'video';