Skip to content

Commit d01105b

Browse files
committed
Multiple modules: automatically fill in PPID for DFP video URLs
prebid#8151
1 parent 0a5fc05 commit d01105b

File tree

5 files changed

+102
-16
lines changed

5 files changed

+102
-16
lines changed

modules/dfpAdServerVideo.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { auctionManager } from '../src/auctionManager.js';
1111
import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js';
1212
import * as events from '../src/events.js';
1313
import CONSTANTS from '../src/constants.json';
14+
import {getPPID} from '../src/adserver.js';
1415

1516
/**
1617
* @typedef {Object} DfpVideoParams
@@ -118,6 +119,13 @@ export function buildDfpVideoUrl(options) {
118119
const uspConsent = uspDataHandler.getConsentData();
119120
if (uspConsent) { queryParams.us_privacy = uspConsent; }
120121

122+
if (!queryParams.ppid) {
123+
const ppid = getPPID();
124+
if (ppid != null) {
125+
queryParams.ppid = ppid;
126+
}
127+
}
128+
121129
return buildUrl(Object.assign({
122130
protocol: 'https',
123131
host: 'securepubads.g.doubleclick.net',

modules/userId/index.js

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ import {
151151
timestamp,
152152
isEmpty
153153
} from '../../src/utils.js';
154+
import {getPPID as coreGetPPID} from '../../src/adserver.js';
154155

155156
const MODULE_NAME = 'User ID';
156157
const COOKIE = 'cookie';
@@ -643,6 +644,19 @@ function idSystemInitializer({delay = delayFor} = {}) {
643644

644645
let initIdSystem;
645646

647+
function getPPID() {
648+
// userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com
649+
const matchingUserId = ppidSource && (getUserIdsAsEids() || []).find(userID => userID.source === ppidSource);
650+
if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') {
651+
const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, '');
652+
if (ppidValue.length >= 32 && ppidValue.length <= 150) {
653+
return ppidValue;
654+
} else {
655+
logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`);
656+
}
657+
}
658+
}
659+
646660
/**
647661
* Hook is executed before adapters, but after consentManagement. Consent data is requied because
648662
* this module requires GDPR consent with Purpose #1 to save data locally.
@@ -659,23 +673,16 @@ export function requestBidsHook(fn, reqBidsConfigObj, {delay = delayFor} = {}) {
659673
]).then(() => {
660674
// pass available user id data to bid adapters
661675
addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules);
662-
663-
// userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com
664-
const matchingUserId = ppidSource && (getUserIdsAsEids() || []).find(userID => userID.source === ppidSource);
665-
if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') {
666-
const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, '');
667-
if (ppidValue.length >= 32 && ppidValue.length <= 150) {
668-
if (isGptPubadsDefined()) {
669-
window.googletag.pubads().setPublisherProvidedId(ppidValue);
670-
} else {
671-
window.googletag = window.googletag || {};
672-
window.googletag.cmd = window.googletag.cmd || [];
673-
window.googletag.cmd.push(function() {
674-
window.googletag.pubads().setPublisherProvidedId(ppidValue);
675-
});
676-
}
676+
const ppid = getPPID();
677+
if (ppid) {
678+
if (isGptPubadsDefined()) {
679+
window.googletag.pubads().setPublisherProvidedId(ppid);
677680
} else {
678-
logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`);
681+
window.googletag = window.googletag || {};
682+
window.googletag.cmd = window.googletag.cmd || [];
683+
window.googletag.cmd.push(function() {
684+
window.googletag.pubads().setPublisherProvidedId(ppid);
685+
});
679686
}
680687
}
681688

@@ -968,6 +975,7 @@ function updateSubmodules() {
968975
if (!addedUserIdHook && submodules.length) {
969976
// priority value 40 will load after consentManagement with a priority of 50
970977
getGlobal().requestBids.before(requestBidsHook, 40);
978+
coreGetPPID.after((next) => next(getPPID()));
971979
logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name));
972980
addedUserIdHook = true;
973981
}

src/adserver.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { formatQS } from './utils.js';
22
import { targeting } from './targeting.js';
3+
import {hook} from './hook.js';
34

45
// Adserver parent class
56
const AdServer = function(attr) {
@@ -11,6 +12,7 @@ const AdServer = function(attr) {
1112
};
1213

1314
// DFP ad server
15+
// TODO: this seems to be unused?
1416
export function dfpAdserver(options, urlComponents) {
1517
var adserver = new AdServer(options);
1618
adserver.urlComponents = urlComponents;
@@ -53,3 +55,8 @@ export function dfpAdserver(options, urlComponents) {
5355

5456
return adserver;
5557
};
58+
59+
/**
60+
* return the GAM PPID, if available (eid for the userID configured with `userSync.ppidSource`)
61+
*/
62+
export const getPPID = hook('sync', () => undefined);

test/spec/modules/dfpAdServerVideo_spec.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { auctionManager } from 'src/auctionManager.js';
1010
import { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js';
1111
import * as adpod from 'modules/adpod.js';
1212
import { server } from 'test/mocks/xhr.js';
13+
import * as adServer from 'src/adserver.js';
14+
import {deepClone} from 'src/utils.js';
15+
import {hook} from '../../../src/hook.js';
1316

1417
const bid = {
1518
videoCacheKey: 'abc',
@@ -20,6 +23,10 @@ const bid = {
2023
};
2124

2225
describe('The DFP video support module', function () {
26+
before(() => {
27+
hook.ready();
28+
});
29+
2330
it('should make a legal request URL when given the required params', function () {
2431
const url = parse(buildDfpVideoUrl({
2532
adUnit: adUnit,
@@ -226,6 +233,44 @@ describe('The DFP video support module', function () {
226233
gdprDataHandlerStub.restore();
227234
});
228235

236+
describe('GAM PPID', () => {
237+
let ppid;
238+
let getPPIDStub;
239+
beforeEach(() => {
240+
getPPIDStub = sinon.stub(adServer, 'getPPID').callsFake(() => ppid);
241+
});
242+
afterEach(() => {
243+
getPPIDStub.restore();
244+
});
245+
246+
Object.entries({
247+
'params': {params: {'iu': 'mock/unit'}},
248+
'url': {url: 'https://video.adserver.mock/', params: {'iu': 'mock/unit'}}
249+
}).forEach(([t, opts]) => {
250+
describe(`when using ${t}`, () => {
251+
function buildUrlAndGetParams() {
252+
const url = parse(buildDfpVideoUrl(Object.assign({
253+
adUnit: adUnit,
254+
bid: deepClone(bid),
255+
}, opts)));
256+
return utils.parseQS(url.query);
257+
}
258+
259+
it('should be included if available', () => {
260+
ppid = 'mockPPID';
261+
const q = buildUrlAndGetParams();
262+
expect(q.ppid).to.equal('mockPPID');
263+
});
264+
265+
it('should not be included if not available', () => {
266+
ppid = undefined;
267+
const q = buildUrlAndGetParams();
268+
expect(q.hasOwnProperty('ppid')).to.be.false;
269+
})
270+
})
271+
})
272+
})
273+
229274
describe('special targeting unit test', function () {
230275
const allTargetingData = {
231276
'hb_format': 'video',

test/spec/modules/userId_spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import * as mockGpt from '../integration/faker/googletag.js';
5252
import 'src/prebid.js';
5353
import {hook} from '../../../src/hook.js';
5454
import {mockGdprConsent} from '../../helpers/consentData.js';
55+
import {getPPID} from '../../../src/adserver.js';
5556

5657
let assert = require('chai').assert;
5758
let expect = require('chai').expect;
@@ -445,6 +446,23 @@ describe('User ID', function () {
445446
});
446447
});
447448

449+
it('should make PPID available to core', () => {
450+
init(config);
451+
setSubmoduleRegistry([sharedIdSystemSubmodule]);
452+
const id = 'thishastobelongerthan32characters';
453+
config.setConfig({
454+
userSync: {
455+
ppid: 'pubcid.org',
456+
userIds: [
457+
{ name: 'pubCommonId', value: {'pubcid': id} },
458+
]
459+
}
460+
});
461+
return getGlobal().refreshUserIds().then(() => {
462+
expect(getPPID()).to.eql(id);
463+
})
464+
});
465+
448466
describe('refreshing before init is complete', () => {
449467
const MOCK_ID = {'MOCKID': '1111'};
450468
let mockIdCallback;

0 commit comments

Comments
 (0)