Skip to content

Commit

Permalink
updated logic for userSync - new field filterSettings (prebid#2499)
Browse files Browse the repository at this point in the history
* initial commit - add filterSettings config option for userSync

* remove commented code

* refactored filterSettings logic, added filter default and changed bidders wildcard

* update logic to support all config type
  • Loading branch information
jsnellbaker authored and AlessandroDG committed Sep 13, 2018
1 parent 03df9c3 commit 4107d8e
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 8 deletions.
5 changes: 3 additions & 2 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,10 @@ export function newBidder(spec) {

function registerSyncs(responses, gdprConsent) {
if (spec.getUserSyncs) {
let filterConfig = config.getConfig('userSync.filterSettings');
let syncs = spec.getUserSyncs({
iframeEnabled: config.getConfig('userSync.iframeEnabled'),
pixelEnabled: config.getConfig('userSync.pixelEnabled'),
iframeEnabled: !!(config.getConfig('userSync.iframeEnabled') || (filterConfig && (filterConfig.iframe || filterConfig.all))),
pixelEnabled: !!(config.getConfig('userSync.pixelEnabled') || (filterConfig && (filterConfig.image || filterConfig.all))),
}, responses, gdprConsent);
if (syncs) {
if (!Array.isArray(syncs)) {
Expand Down
94 changes: 88 additions & 6 deletions src/userSync.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as utils from 'src/utils';
import { config } from 'src/config';
import includes from 'core-js/library/fn/array/includes';

// Set userSync default values
config.setDefaults({
Expand Down Expand Up @@ -28,6 +29,12 @@ export function newUserSync(userSyncDependencies) {
// How many bids for each adapter
let numAdapterBids = {};

// for now - default both to false in case filterSettings config is absent/misconfigured
let permittedPixels = {
image: false,
iframe: false
}

// Use what is in config by default
let usConfig = userSyncDependencies.config;
// Update if it's (re)set
Expand Down Expand Up @@ -77,7 +84,7 @@ export function newUserSync(userSyncDependencies) {
* @private
*/
function fireImagePixels() {
if (!usConfig.pixelEnabled) {
if (!(usConfig.pixelEnabled || permittedPixels.image)) {
return;
}
// Randomize the order of the pixels before firing
Expand All @@ -97,7 +104,7 @@ export function newUserSync(userSyncDependencies) {
* @private
*/
function loadIframes() {
if (!usConfig.iframeEnabled) {
if (!(usConfig.iframeEnabled || permittedPixels.iframe)) {
return;
}
// Randomize the order of these syncs just like the pixels above
Expand Down Expand Up @@ -148,15 +155,89 @@ export function newUserSync(userSyncDependencies) {
if (Number(numAdapterBids[bidder]) >= usConfig.syncsPerBidder) {
return utils.logWarn(`Number of user syncs exceeded for "${bidder}"`);
}
// All bidders are enabled by default. If specified only register for enabled bidders.
let hasEnabledBidders = usConfig.enabledBidders && usConfig.enabledBidders.length;
if (hasEnabledBidders && usConfig.enabledBidders.indexOf(bidder) < 0) {
return utils.logWarn(`Bidder "${bidder}" not supported`);

if (usConfig.filterSettings) {
if (shouldBidderBeBlocked(type, bidder)) {
return utils.logWarn(`Bidder '${bidder}' is not permitted to register their userSync ${type} pixels as per filterSettings config.`);
}
// TODO remove this else if code that supports deprecated fields (sometime in 2.x); for now - only run if filterSettings config is not present
} else if (usConfig.enabledBidders && usConfig.enabledBidders.length && usConfig.enabledBidders.indexOf(bidder) < 0) {
return utils.logWarn(`Bidder "${bidder}" not permitted to register their userSync pixels.`);
}

// the bidder's pixel has passed all checks and is allowed to register
queue[type].push([bidder, url]);
numAdapterBids = incrementAdapterBids(numAdapterBids, bidder);
};

/**
* @function shouldBidderBeBlocked
* @summary Check filterSettings logic to determine if the bidder should be prevented from registering their userSync tracker
* @private
* @param {string} type The type of the sync; either image or iframe
* @param {string} bidder The name of the adapter. e.g. "rubicon"
* @returns {boolean} true => bidder is not allowed to register; false => bidder can register
*/
function shouldBidderBeBlocked(type, bidder) {
let filterConfig = usConfig.filterSettings;

// apply the filter check if the config object is there (eg filterSettings.iframe exists) and if the config object is properly setup
if (isFilterConfigValid(filterConfig, type)) {
permittedPixels[type] = true;

let activeConfig = (filterConfig.all) ? filterConfig.all : filterConfig[type];
let biddersToFilter = (activeConfig.bidders === '*') ? [bidder] : activeConfig.bidders;
let filterType = activeConfig.filter || 'include'; // set default if undefined

// return true if the bidder is either: not part of the include (ie outside the whitelist) or part of the exclude (ie inside the blacklist)
const checkForFiltering = {
'include': (bidders, bidder) => !includes(bidders, bidder),
'exclude': (bidders, bidder) => includes(bidders, bidder)
}
return checkForFiltering[filterType](biddersToFilter, bidder);
}
return false;
}

/**
* @function isFilterConfigValid
* @summary Check if the filterSettings object in the userSync config is setup properly
* @private
* @param {object} filterConfig sub-config object taken from filterSettings
* @param {string} type The type of the sync; either image or iframe
* @returns {boolean} true => config is setup correctly, false => setup incorrectly or filterConfig[type] is not present
*/
function isFilterConfigValid(filterConfig, type) {
if (filterConfig.all && filterConfig[type]) {
utils.logWarn(`Detected presence of the "filterSettings.all" and "filterSettings.${type}" in userSync config. You cannot mix "all" with "iframe/image" configs; they are mutually exclusive.`);
return false;
}

let activeConfig = (filterConfig.all) ? filterConfig.all : filterConfig[type];
let activeConfigName = (filterConfig.all) ? 'all' : type;

// if current pixel type isn't part of the config's logic, skip rest of the config checks...
// we return false to skip subsequent filter checks in shouldBidderBeBlocked() function
if (!activeConfig) {
return false;
}

let filterField = activeConfig.filter;
let biddersField = activeConfig.bidders;

if (filterField && filterField !== 'include' && filterField !== 'exclude') {
utils.logWarn(`UserSync "filterSettings.${activeConfigName}.filter" setting '${filterField}' is not a valid option; use either 'include' or 'exclude'.`);
return false;
}

if (biddersField !== '*' && !(Array.isArray(biddersField) && biddersField.length > 0 && biddersField.every(bidderInList => utils.isStr(bidderInList) && bidderInList !== '*'))) {
utils.logWarn(`Detected an invalid setup in userSync "filterSettings.${activeConfigName}.bidders"; use either '*' (to represent all bidders) or an array of bidders.`);
return false;
}

return true;
}

/**
* @function syncUsers
* @summary Trigger all the user syncs based on publisher-defined timeout
Expand Down Expand Up @@ -207,4 +288,5 @@ export const userSync = newUserSync({
* @property {boolean} iframeEnabled
* @property {int} syncsPerBidder
* @property {string[]} enabledBidders
* @property {Object} filterSettings
*/
149 changes: 149 additions & 0 deletions test/spec/userSync_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,153 @@ describe('user sync', () => {
expect(triggerPixelStub.getCall(0)).to.not.be.null;
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com');
});

it('should register both image and iframe pixels with filterSettings.all config', () => {
const userSync = newTestUserSync({
filterSettings: {
all: {
bidders: ['atestBidder', 'testBidder'],
filter: 'include'
},
}
});
userSync.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync.syncUsers();
expect(triggerPixelStub.getCall(0)).to.not.be.null;
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com/1');
expect(insertUserSyncIframeStub.getCall(0)).to.not.be.null;
expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe');
});

it('should register iframe and not register image pixels based on filterSettings config', () => {
const userSync = newTestUserSync({
filterSettings: {
image: {
bidders: '*',
filter: 'exclude'
},
iframe: {
bidders: ['testBidder']
}
}
});
userSync.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync.syncUsers();
expect(triggerPixelStub.getCall(0)).to.be.null;
expect(insertUserSyncIframeStub.getCall(0)).to.not.be.null;
expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe');
});

it('should throw a warning and default to basic resgistration rules when filterSettings config is invalid', () => {
// invalid config - passed invalid filter option
const userSync1 = newTestUserSync({
filterSettings: {
iframe: {
bidders: ['testBidder'],
filter: 'includes'
}
}
});
userSync1.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync1.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync1.syncUsers();
expect(logWarnStub.getCall(0).args[0]).to.exist;
expect(triggerPixelStub.getCall(0)).to.not.be.null;
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com/1');
expect(insertUserSyncIframeStub.getCall(0)).to.be.null;

// invalid config - bidders is not an array of strings
const userSync2 = newTestUserSync({
filterSettings: {
iframe: {
bidders: ['testBidder', 0],
filter: 'include'
}
}
});
userSync2.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync2.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync2.syncUsers();
expect(logWarnStub.getCall(1).args[0]).to.exist;
expect(triggerPixelStub.getCall(1)).to.not.be.null;
expect(triggerPixelStub.getCall(1).args[0]).to.exist.and.to.equal('http://example.com/1');
expect(insertUserSyncIframeStub.getCall(0)).to.be.null;

// invalid config - bidders list includes wildcard
const userSync3 = newTestUserSync({
filterSettings: {
iframe: {
bidders: ['testBidder', '*'],
filter: 'include'
}
}
});
userSync3.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync3.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync3.syncUsers();
expect(logWarnStub.getCall(2).args[0]).to.exist;
expect(triggerPixelStub.getCall(2)).to.not.be.null;
expect(triggerPixelStub.getCall(2).args[0]).to.exist.and.to.equal('http://example.com/1');
expect(insertUserSyncIframeStub.getCall(0)).to.be.null;

// invalid config - incorrect wildcard
const userSync4 = newTestUserSync({
filterSettings: {
iframe: {
bidders: '***',
filter: 'include'
}
}
});
userSync4.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync4.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync4.syncUsers();
expect(logWarnStub.getCall(3).args[0]).to.exist;
expect(triggerPixelStub.getCall(3)).to.not.be.null;
expect(triggerPixelStub.getCall(3).args[0]).to.exist.and.to.equal('http://example.com/1');
expect(insertUserSyncIframeStub.getCall(0)).to.be.null;

// invalid config - missing bidders field
const userSync5 = newTestUserSync({
filterSettings: {
iframe: {
filter: 'include'
}
}
});
userSync5.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync5.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync5.syncUsers();
expect(logWarnStub.getCall(4).args[0]).to.exist;
expect(triggerPixelStub.getCall(4)).to.not.be.null;
expect(triggerPixelStub.getCall(4).args[0]).to.exist.and.to.equal('http://example.com/1');
expect(insertUserSyncIframeStub.getCall(0)).to.be.null;
});

it('should overwrite logic of deprecated fields when filterSettings is defined', () => {
const userSync = newTestUserSync({
pixelsEnabled: false,
iframeEnabled: true,
enabledBidders: ['ctestBidder'],
filterSettings: {
image: {
bidders: '*',
filter: 'include'
},
iframe: {
bidders: ['testBidder'],
filter: 'exclude'
}
}
});
userSync.registerSync('image', 'atestBidder', 'http://example.com/1');
userSync.registerSync('iframe', 'testBidder', 'http://example.com/iframe');
userSync.syncUsers();
expect(logWarnStub.getCall(0).args[0]).to.exist;
expect(triggerPixelStub.getCall(0)).to.not.be.null;
expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com/1');
expect(insertUserSyncIframeStub.getCall(0)).to.be.null;
});
});

0 comments on commit 4107d8e

Please sign in to comment.