Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Publisher Common ID module (#2150)
* Add Publisher Common ID module * Specify base 10 for parseInt in PubCommonID
- Loading branch information
Showing
4 changed files
with
317 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/** | ||
* This modules adds Publisher Common ID support to prebid.js. It's a simple numeric id | ||
* stored in the page's domain. When the module is included, an id is generated if needed, | ||
* persisted as a cookie, and automatically appended to all the bidRequest as bid.crumbs.pubcid. | ||
*/ | ||
import * as utils from 'src/utils' | ||
import { config } from 'src/config'; | ||
|
||
const COOKIE_NAME = '_pubcid'; | ||
const DEFAULT_EXPIRES = 2628000; // 5-year worth of minutes | ||
const PUB_COMMON = 'PublisherCommonId'; | ||
|
||
var pubcidEnabled = true; | ||
var interval = DEFAULT_EXPIRES; | ||
|
||
export function isPubcidEnabled() { return pubcidEnabled; } | ||
export function getExpInterval() { return interval; } | ||
|
||
/** | ||
* Decorate ad units with pubcid. This hook function is called before the | ||
* real pbjs.requestBids is invoked, and can modify its parameter. The cookie is | ||
* not updated until this function is called. | ||
* @param {Object} config This is the same parameter as pbjs.requestBids, and config.adUnits will be updated. | ||
* @param {function} next The next function in the chain | ||
*/ | ||
|
||
export function requestBidHook(config, next) { | ||
let adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits; | ||
let pubcid = null; | ||
|
||
// Pass control to the next function if not enabled | ||
if (!pubcidEnabled) { | ||
return next.apply(this, arguments); | ||
} | ||
|
||
if (typeof window[PUB_COMMON] === 'object') { | ||
// If the page includes its own pubcid object, then use that instead. | ||
pubcid = window[PUB_COMMON].getId(); | ||
utils.logMessage(PUB_COMMON + ': pubcid = ' + pubcid); | ||
} else { | ||
// Otherwise get the existing cookie or create a new id | ||
pubcid = getCookie(COOKIE_NAME) || utils.generateUUID(); | ||
|
||
// Update the cookie with the latest expiration date | ||
setCookie(COOKIE_NAME, pubcid, interval); | ||
utils.logMessage('pbjs: pubcid = ' + pubcid); | ||
} | ||
|
||
// Append pubcid to each bid object, which will be incorporated | ||
// into bid requests later. | ||
if (adUnits && pubcid) { | ||
adUnits.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
Object.assign(bid, {crumbs: {pubcid}}); | ||
}); | ||
}); | ||
} | ||
return next.apply(this, arguments); | ||
} | ||
|
||
// Helper to set a cookie | ||
export function setCookie(name, value, expires) { | ||
let expTime = new Date(); | ||
expTime.setTime(expTime.getTime() + expires * 1000 * 60); | ||
window.document.cookie = name + '=' + encodeURIComponent(value) + ';path=/;expires=' + | ||
expTime.toGMTString(); | ||
} | ||
|
||
// Helper to read a cookie | ||
export function getCookie(name) { | ||
let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)'); | ||
return m ? decodeURIComponent(m[2]) : null; | ||
} | ||
|
||
/** | ||
* Configuration function | ||
* @param {boolean} enable Enable or disable pubcid. By default the module is enabled. | ||
* @param {number} expInterval Expiration interval of the cookie in minutes. | ||
*/ | ||
|
||
export function setConfig({ enable = true, expInterval = DEFAULT_EXPIRES } = {}) { | ||
pubcidEnabled = enable; | ||
interval = parseInt(expInterval, 10); | ||
if (isNaN(interval)) { | ||
interval = DEFAULT_EXPIRES; | ||
} | ||
} | ||
|
||
/** | ||
* Initialize module by 1) subscribe to configuration changes and 2) register hook | ||
*/ | ||
export function initPubcid() { | ||
config.getConfig('pubcid', config => setConfig(config.pubcid)); | ||
|
||
if (utils.cookiesAreEnabled()) { | ||
$$PREBID_GLOBAL$$.requestBids.addHook(requestBidHook); | ||
} | ||
} | ||
|
||
initPubcid(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import { | ||
requestBidHook, | ||
getCookie, | ||
setCookie, | ||
setConfig, | ||
isPubcidEnabled, | ||
getExpInterval, | ||
initPubcid } from 'modules/pubCommonId'; | ||
import { getAdUnits } from 'test/fixtures/fixtures'; | ||
import * as auctionModule from 'src/auction'; | ||
import { registerBidder } from 'src/adapters/bidderFactory'; | ||
import * as utils from 'src/utils'; | ||
|
||
var assert = require('chai').assert; | ||
var expect = require('chai').expect; | ||
|
||
const COOKIE_NAME = '_pubcid'; | ||
const TIMEOUT = 2000; | ||
|
||
describe('Publisher Common ID', function () { | ||
describe('Decorate adUnits', function () { | ||
it('Check same cookie', function () { | ||
let adUnits1 = getAdUnits(); | ||
let adUnits2 = getAdUnits(); | ||
let innerAdUnits1; | ||
let innerAdUnits2; | ||
let pubcid = getCookie(COOKIE_NAME); | ||
|
||
expect(pubcid).to.be.null; // there should be no cookie initially | ||
|
||
requestBidHook({adUnits: adUnits1}, (config) => { innerAdUnits1 = config.adUnits }); | ||
pubcid = getCookie(COOKIE_NAME); // cookies is created after requestbidHook | ||
|
||
innerAdUnits1.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
expect(bid.crumbs.pubcid).to.equal(pubcid); | ||
}); | ||
}); | ||
requestBidHook({adUnits: adUnits2}, (config) => { innerAdUnits2 = config.adUnits }); | ||
assert.deepEqual(innerAdUnits1, innerAdUnits2); | ||
}); | ||
|
||
it('Check different cookies', function () { | ||
let adUnits1 = getAdUnits(); | ||
let adUnits2 = getAdUnits(); | ||
let innerAdUnits1; | ||
let innerAdUnits2; | ||
let pubcid1; | ||
let pubcid2; | ||
|
||
requestBidHook({adUnits: adUnits1}, (config) => { innerAdUnits1 = config.adUnits }); | ||
pubcid1 = getCookie(COOKIE_NAME); // get first cookie | ||
setCookie(COOKIE_NAME, '', -1); // erase cookie | ||
|
||
innerAdUnits1.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
expect(bid.crumbs.pubcid).to.equal(pubcid1); | ||
}); | ||
}); | ||
|
||
requestBidHook({adUnits: adUnits2}, (config) => { innerAdUnits2 = config.adUnits }); | ||
pubcid2 = getCookie(COOKIE_NAME); // get second cookie | ||
|
||
innerAdUnits2.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
expect(bid.crumbs.pubcid).to.equal(pubcid2); | ||
}); | ||
}); | ||
|
||
expect(pubcid1).to.not.equal(pubcid2); | ||
}); | ||
|
||
it('Check new cookie', function () { | ||
let adUnits = getAdUnits(); | ||
let innerAdUnits; | ||
let pubcid = utils.generateUUID(); | ||
|
||
setCookie(COOKIE_NAME, pubcid, 600); | ||
requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); | ||
innerAdUnits.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
expect(bid.crumbs.pubcid).to.equal(pubcid); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Configuration', function () { | ||
it('empty config', function () { | ||
// this should work as usual | ||
setConfig({}); | ||
let adUnits = getAdUnits(); | ||
let innerAdUnits; | ||
requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); | ||
let pubcid = getCookie(COOKIE_NAME); | ||
innerAdUnits.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
expect(bid.crumbs.pubcid).to.equal(pubcid); | ||
}); | ||
}); | ||
}); | ||
|
||
it('disable', function () { | ||
setConfig({enable: false}); | ||
setCookie(COOKIE_NAME, '', -1); // erase cookie | ||
let adUnits = getAdUnits(); | ||
let unmodified = getAdUnits(); | ||
let innerAdUnits; | ||
expect(isPubcidEnabled()).to.be.false; | ||
requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); | ||
expect(getCookie(COOKIE_NAME)).to.be.null; | ||
assert.deepEqual(innerAdUnits, unmodified); | ||
setConfig({enable: true}); // reset | ||
requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); | ||
innerAdUnits.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
}); | ||
}); | ||
}); | ||
|
||
it('change expiration time', function () { | ||
setConfig({expInterval: 100}); | ||
setCookie(COOKIE_NAME, '', -1); // erase cookie | ||
expect(getExpInterval()).to.equal(100); | ||
let adUnits = getAdUnits(); | ||
let innerAdUnits; | ||
requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); | ||
innerAdUnits.every((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
}); | ||
}) | ||
}); | ||
}); | ||
|
||
describe('Invoking requestBid', function () { | ||
let createAuctionStub; | ||
let adUnits; | ||
let adUnitCodes; | ||
let capturedReqs; | ||
let sampleSpec = { | ||
code: 'sampleBidder', | ||
isBidRequestValid: () => {}, | ||
buildRequest: (reqs) => {}, | ||
interpretResponse: () => {}, | ||
getUserSyncs: () => {} | ||
}; | ||
|
||
beforeEach(() => { | ||
adUnits = [{ | ||
code: 'adUnit-code', | ||
mediaTypes: { | ||
banner: {}, | ||
native: {}, | ||
}, | ||
sizes: [[300, 200], [300, 600]], | ||
bids: [ | ||
{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}} | ||
] | ||
}]; | ||
adUnitCodes = ['adUnit-code']; | ||
let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: TIMEOUT}); | ||
createAuctionStub = sinon.stub(auctionModule, 'newAuction'); | ||
createAuctionStub.returns(auction); | ||
initPubcid(); | ||
registerBidder(sampleSpec); | ||
}); | ||
|
||
afterEach(() => { | ||
auctionModule.newAuction.restore(); | ||
}); | ||
|
||
it('test hook', function() { | ||
$$PREBID_GLOBAL$$.requestBids({adUnits}); | ||
adUnits.forEach((unit) => { | ||
unit.bids.forEach((bid) => { | ||
expect(bid).to.have.deep.property('crumbs.pubcid'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |