Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

updated logic for userSync - new field filterSettings #2499

Merged
merged 5 commits into from
Jun 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});
});