diff --git a/gulpHelpers.js b/gulpHelpers.js index f20a2673ade..04428133347 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -6,12 +6,14 @@ const MANIFEST = 'package.json'; const through = require('through2'); const _ = require('lodash'); const gutil = require('gulp-util'); +const submodules = require('./modules/.submodules.json'); const MODULE_PATH = './modules'; const BUILD_PATH = './build/dist'; const DEV_PATH = './build/dev'; const ANALYTICS_PATH = '../analytics'; + // get only subdirectories that contain package.json with 'main' property function isModuleDirectory(filePath) { try { @@ -39,7 +41,9 @@ module.exports = { .replace(/\/>/g, '\\/>'); }, getArgModules() { - var modules = (argv.modules || '').split(',').filter(module => !!module); + var modules = (argv.modules || '') + .split(',') + .filter(module => !!module); try { if (modules.length === 1 && path.extname(modules[0]).toLowerCase() === '.json') { @@ -56,6 +60,15 @@ module.exports = { }); } + Object.keys(submodules).forEach(parentModule => { + if ( + !modules.includes(parentModule) && + modules.some(module => submodules[parentModule].includes(module)) + ) { + modules.unshift(parentModule); + } + }); + return modules; }, getModules: _.memoize(function(externalModules) { diff --git a/modules/.submodules.json b/modules/.submodules.json new file mode 100644 index 00000000000..f321e075208 --- /dev/null +++ b/modules/.submodules.json @@ -0,0 +1,9 @@ +{ + "userId": [ + "digiTrustIdSystem", + "id5IdSystem" + ], + "adpod": [ + "freeWheelAdserverVideo" + ] +} diff --git a/modules/adpod.js b/modules/adpod.js index 021d4722f53..46671e9fb0a 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -16,16 +16,17 @@ import * as utils from '../src/utils'; import { addBidToAuction, doCallbacksIfTimedout, AUCTION_IN_PROGRESS, callPrebidCache } from '../src/auction'; import { checkAdUnitSetup } from '../src/prebid'; import { checkVideoBidSetup } from '../src/video'; -import { setupBeforeHookFnOnce } from '../src/hook'; +import { setupBeforeHookFnOnce, module } from '../src/hook'; import { store } from '../src/videoCache'; import { config } from '../src/config'; import { ADPOD } from '../src/mediaTypes'; import Set from 'core-js/library/fn/set'; import find from 'core-js/library/fn/array/find'; + const from = require('core-js/library/fn/array/from'); -export const TARGETING_KEY_PB_CAT_DUR = 'hb_pb_cat_dur'; -export const TARGETING_KEY_CACHE_ID = 'hb_cache_id' +const TARGETING_KEY_PB_CAT_DUR = 'hb_pb_cat_dur'; +const TARGETING_KEY_CACHE_ID = 'hb_cache_id'; let queueTimeDelay = 50; let queueSizeLimit = 5; @@ -385,12 +386,13 @@ config.getConfig('adpod', config => adpodSetConfig(config.adpod)); /** * This function initializes the adpod module's hooks. This is called by the corresponding adserver video module. */ -export function initAdpodHooks() { +function initAdpodHooks() { setupBeforeHookFnOnce(callPrebidCache, callPrebidCacheHook); setupBeforeHookFnOnce(checkAdUnitSetup, checkAdUnitSetupHook); setupBeforeHookFnOnce(checkVideoBidSetup, checkVideoBidSetupHook); } +initAdpodHooks() /** * * @param {Array[Object]} bids list of 'winning' bids that need to be cached @@ -428,3 +430,24 @@ export function sortByPricePerSecond(a, b) { } return 0; } + +const sharedMethods = { + TARGETING_KEY_PB_CAT_DUR: TARGETING_KEY_PB_CAT_DUR, + TARGETING_KEY_CACHE_ID: TARGETING_KEY_CACHE_ID, + 'sortByPricePerSecond': sortByPricePerSecond, + 'callPrebidCacheAfterAuction': callPrebidCacheAfterAuction +} +Object.freeze(sharedMethods); + +module('adpod', function shareAdpodUtilities(...args) { + if (!utils.isPlainObject(args[0])) { + utils.logError('Adpod module needs plain object to share methods with submodule'); + return; + } + function addMethods(object, func) { + for (let name in func) { + object[name] = func[name]; + } + } + addMethods(args[0], sharedMethods); +}); diff --git a/modules/digiTrustIdSystem.js b/modules/digiTrustIdSystem.js index 454e6864846..1db65031848 100644 --- a/modules/digiTrustIdSystem.js +++ b/modules/digiTrustIdSystem.js @@ -12,7 +12,7 @@ // import { config } from 'src/config'; import * as utils from '../src/utils' import { ajax } from '../src/ajax'; -import { attachIdSystem } from '../modules/userId'; +import { submodule } from '../src/hook'; // import { getGlobal } from 'src/prebidGlobal'; /** @@ -336,4 +336,4 @@ export const digiTrustIdSubmodule = { _testInit: surfaceTestHook }; -attachIdSystem(digiTrustIdSubmodule); +submodule('userId', digiTrustIdSubmodule); diff --git a/modules/freeWheelAdserverVideo.js b/modules/freeWheelAdserverVideo.js index c81f3356d71..a93e5ab9159 100644 --- a/modules/freeWheelAdserverVideo.js +++ b/modules/freeWheelAdserverVideo.js @@ -7,8 +7,7 @@ import { auctionManager } from '../src/auctionManager'; import { groupBy, deepAccess, logError, compareOn } from '../src/utils'; import { config } from '../src/config'; import { ADPOD } from '../src/mediaTypes'; -import { initAdpodHooks, TARGETING_KEY_PB_CAT_DUR, TARGETING_KEY_CACHE_ID, callPrebidCacheAfterAuction, sortByPricePerSecond } from './adpod'; -import { getHook } from '../src/hook'; +import { getHook, submodule } from '../src/hook'; export function notifyTranslationModule(fn) { fn.call(this, 'freewheel'); @@ -37,7 +36,7 @@ export function getTargeting({codes, callback} = {}) { let bids = getBidsForAdpod(bidsReceived, adPodAdUnits); bids = (competiveExclusionEnabled || deferCachingEnabled) ? getExclusiveBids(bids) : bids; - bids.sort(sortByPricePerSecond); + bids.sort(adpodUtils.sortByPricePerSecond); let targeting = {}; if (deferCachingEnabled === false) { @@ -50,13 +49,13 @@ export function getTargeting({codes, callback} = {}) { .forEach((bid, index, arr) => { if (bid.video.durationBucket <= adPodDurationSeconds) { adPodTargeting.push({ - [TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] + [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[adpodUtils.TARGETING_KEY_PB_CAT_DUR] }); adPodDurationSeconds -= bid.video.durationBucket; } if (index === arr.length - 1 && adPodTargeting.length > 0) { adPodTargeting.push({ - [TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[TARGETING_KEY_CACHE_ID] + [adpodUtils.TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[adpodUtils.TARGETING_KEY_CACHE_ID] }); } }); @@ -79,7 +78,7 @@ export function getTargeting({codes, callback} = {}) { }); }); - callPrebidCacheAfterAuction(bidsToCache, function(error, bidsSuccessfullyCached) { + adpodUtils.callPrebidCacheAfterAuction(bidsToCache, function(error, bidsSuccessfullyCached) { if (error) { callback(error, null); } else { @@ -89,12 +88,12 @@ export function getTargeting({codes, callback} = {}) { groupedBids[adUnitCode].forEach((bid, index, arr) => { adPodTargeting.push({ - [TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] + [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[adpodUtils.TARGETING_KEY_PB_CAT_DUR] }); if (index === arr.length - 1 && adPodTargeting.length > 0) { adPodTargeting.push({ - [TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[TARGETING_KEY_CACHE_ID] + [adpodUtils.TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[adpodUtils.TARGETING_KEY_CACHE_ID] }); } }); @@ -126,8 +125,8 @@ function getAdPodAdUnits(codes) { */ function getExclusiveBids(bidsReceived) { let bids = bidsReceived - .map((bid) => Object.assign({}, bid, {[TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR]})); - bids = groupBy(bids, TARGETING_KEY_PB_CAT_DUR); + .map((bid) => Object.assign({}, bid, {[adpodUtils.TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[adpodUtils.TARGETING_KEY_PB_CAT_DUR]})); + bids = groupBy(bids, adpodUtils.TARGETING_KEY_PB_CAT_DUR); let filteredBids = []; Object.keys(bids).forEach((targetingKey) => { bids[targetingKey].sort(compareOn('responseTimestamp')); @@ -148,7 +147,9 @@ function getBidsForAdpod(bidsReceived, adPodAdUnits) { .filter((bid) => adUnitCodes.indexOf(bid.adUnitCode) != -1 && (bid.video && bid.video.context === ADPOD)) } -initAdpodHooks(); registerVideoSupport('freewheel', { getTargeting: getTargeting }); + +export const adpodUtils = {}; +submodule('adpod', adpodUtils); diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 39ab256a81e..7ed1fdf6bf3 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -5,9 +5,9 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' -import {ajax} from '../src/ajax.js'; -import {isGDPRApplicable, attachIdSystem} from './userId.js'; +import * as utils from '../src/utils' +import {ajax} from '../src/ajax'; +import {submodule} from '../src/hook'; /** @type {Submodule} */ export const id5IdSubmodule = { @@ -37,7 +37,7 @@ export const id5IdSubmodule = { utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); return; } - const hasGdpr = isGDPRApplicable(consentData) ? 1 : 0; + const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? consentData.consentString : ''; const url = `https://id5-sync.com/g/v1/${configParams.partner}.json?gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}`; @@ -57,4 +57,4 @@ export const id5IdSubmodule = { } }; -attachIdSystem(id5IdSubmodule); +submodule('userId', id5IdSubmodule); diff --git a/modules/userId.js b/modules/userId/index.js similarity index 94% rename from modules/userId.js rename to modules/userId/index.js index c80ea21a0a0..2091a6a763a 100644 --- a/modules/userId.js +++ b/modules/userId/index.js @@ -13,7 +13,7 @@ * @name Submodule#getId * @param {SubmoduleParams} configParams * @param {ConsentData} consentData - * @return {(Object|function} id data or a callback, the callback is called on the auction end event + * @return {(Object|function)} id data or a callback, the callback is called on the auction end event */ /** @@ -21,7 +21,7 @@ * @summary decode a stored value for passing to bid requests * @name Submodule#decode * @param {Object|string} value - * @return {(Object|undefined} + * @return {(Object|undefined)} */ /** @@ -68,14 +68,15 @@ */ import find from 'core-js/library/fn/array/find'; -import {config} from '../src/config.js'; -import events from '../src/events.js'; -import * as utils from '../src/utils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {gdprDataHandler} from '../src/adapterManager.js'; +import {config} from '../../src/config'; +import events from '../../src/events'; +import * as utils from '../../src/utils'; +import {getGlobal} from '../../src/prebidGlobal'; +import {gdprDataHandler} from '../../src/adapterManager'; +import CONSTANTS from '../../src/constants.json'; +import {module} from '../../src/hook'; import {unifiedIdSubmodule} from './unifiedIdSystem.js'; import {pubCommonIdSubmodule} from './pubCommonIdSystem.js'; -import CONSTANTS from '../src/constants.json'; const MODULE_NAME = 'User ID'; const COOKIE = 'cookie'; @@ -158,22 +159,13 @@ function getStoredValue(storage) { return storedValue; } -/** - * test if consent module is present, and if GDPR applies - * @param {ConsentData} consentData - * @returns {boolean} - */ -export function isGDPRApplicable(consentData) { - return consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; -} - /** * test if consent module is present, applies, and is valid for local storage or cookies (purpose 1) * @param {ConsentData} consentData * @returns {boolean} */ -export function hasGDPRConsent(consentData) { - if (isGDPRApplicable(consentData)) { +function hasGDPRConsent(consentData) { + if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { if (!consentData.consentString) { return false; } @@ -331,7 +323,7 @@ function getValidSubmoduleConfigs(configRegistry, submoduleRegistry, activeStora if (!config || utils.isEmptyStr(config.name)) { return carry; } - // alidate storage config contains 'type' and 'name' properties with non-empty string values + // Validate storage config contains 'type' and 'name' properties with non-empty string values // 'type' must be a value currently enabled in the browser if (config.storage && !utils.isEmptyStr(config.storage.type) && @@ -409,7 +401,7 @@ export function init(config) { return; } // _pubcid_optout is checked for compatiblility with pubCommonId - if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && (localStorage.getItem('_pbjs_id_optout') && localStorage.getItem('_pubcid_optout'))) { + if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && (localStorage.getItem('_pbjs_id_optout') || localStorage.getItem('_pubcid_optout'))) { utils.logInfo(`${MODULE_NAME} - opt-out localStorage found, exit module`); return; } @@ -431,3 +423,5 @@ init(config); // add submodules after init has been called attachIdSystem(pubCommonIdSubmodule); attachIdSystem(unifiedIdSubmodule); + +module('userId', attachIdSystem); diff --git a/modules/pubCommonIdSystem.js b/modules/userId/pubCommonIdSystem.js similarity index 96% rename from modules/pubCommonIdSystem.js rename to modules/userId/pubCommonIdSystem.js index 39d1feea0ad..f4d6b41a127 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/userId/pubCommonIdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' +import * as utils from '../../src/utils'; /** @type {Submodule} */ export const pubCommonIdSubmodule = { diff --git a/modules/unifiedIdSystem.js b/modules/userId/unifiedIdSystem.js similarity index 95% rename from modules/unifiedIdSystem.js rename to modules/userId/unifiedIdSystem.js index 6b67b7bf5f1..cf86c049d2a 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/userId/unifiedIdSystem.js @@ -5,8 +5,8 @@ * @requires module:modules/userId */ -import * as utils from '../src/utils.js' -import {ajax} from '../src/ajax.js'; +import * as utils from '../../src/utils' +import {ajax} from '../../src/ajax'; /** @type {Submodule} */ export const unifiedIdSubmodule = { diff --git a/modules/userId.md b/modules/userId/userId.md similarity index 72% rename from modules/userId.md rename to modules/userId/userId.md index b36b9b1007c..782e7782554 100644 --- a/modules/userId.md +++ b/modules/userId/userId.md @@ -3,12 +3,12 @@ Example showing `cookie` storage for user id data for both submodules ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: "unifiedId", params: { partner: "prebid", - url: "http://match.adsrvr.org/track/rid?ttd_pid=prebid&fmt=json" + url: "//match.adsrvr.org/track/rid?ttd_pid=prebid&fmt=json" }, storage: { type: "cookie", @@ -28,26 +28,6 @@ pbjs.setConfig({ }); ``` -Example showing `cookie` storage for user id data for id5 submodule -``` -pbjs.setConfig({ - usersync: { - userIds: [{ - name: "id5Id", - params: { - partner: 173 // @TODO: Set your real ID5 partner ID here for production, please ask for one contact@id5.io - }, - storage: { - type: "cookie", - name: "id5id", - expires: 90 - } - }], - syncDelay: 5000 - } -}); -``` - Example showing `localStorage` for user id data for both submodules ``` pbjs.setConfig({ diff --git a/src/hook.js b/src/hook.js index ddf0d134357..220e1c39111 100644 --- a/src/hook.js +++ b/src/hook.js @@ -13,3 +13,16 @@ export function setupBeforeHookFnOnce(baseFn, hookFn, priority = 15) { baseFn.before(hookFn, priority); } } + +export function module(name, install) { + hook('async', function (submodules) { + submodules.forEach(args => install(...args)); + }, name)([]); // will be queued until hook.ready() called in pbjs.processQueue(); +} + +export function submodule(name, ...args) { + getHook(name).before((next, modules) => { + modules.push(args); + next(modules); + }); +} diff --git a/test/spec/modules/freeWheelAdserverVideo_spec.js b/test/spec/modules/freeWheelAdserverVideo_spec.js index 5846774c8b1..ffc690e5a92 100644 --- a/test/spec/modules/freeWheelAdserverVideo_spec.js +++ b/test/spec/modules/freeWheelAdserverVideo_spec.js @@ -1,8 +1,7 @@ import { expect } from 'chai'; -import { getTargeting } from 'modules/freeWheelAdserverVideo'; +import { getTargeting, adpodUtils } from 'modules/freeWheelAdserverVideo'; import { auctionManager } from 'src/auctionManager'; import { config } from 'src/config'; -import * as adpod from 'modules/adpod'; describe('freeWheel adserver module', function() { let amStub; @@ -53,7 +52,7 @@ describe('freeWheel adserver module', function() { amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); amGetAdUnitsStub.returns(adUnits); amStub = sinon.stub(auctionManager, 'getBidsReceived'); - pbcStub = sinon.stub(adpod, 'callPrebidCacheAfterAuction').callsFake(function (...args) { + pbcStub = sinon.stub(adpodUtils, 'callPrebidCacheAfterAuction').callsFake(function (...args) { args[1](null, getBidsReceived()); }); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 60df468a903..a158d2e9fed 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -4,11 +4,11 @@ import { setSubmoduleRegistry, syncDelay, attachIdSystem -} from 'modules/userId'; +} from 'modules/userId/index.js'; import {config} from 'src/config'; import * as utils from 'src/utils'; -import {unifiedIdSubmodule} from 'modules/unifiedIdSystem'; -import {pubCommonIdSubmodule} from 'modules/pubCommonIdSystem'; +import {unifiedIdSubmodule} from 'modules/userId/unifiedIdSystem'; +import {pubCommonIdSubmodule} from 'modules/userId/pubCommonIdSystem'; import {id5IdSubmodule} from 'modules/id5IdSystem'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -327,7 +327,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from unifiedid html5', function(done) { + it('test hook from pubcommonid html5', function(done) { // simulate existing browser local storage values localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); localStorage.setItem('unifiedid_alt_exp', ''); diff --git a/webpack.conf.js b/webpack.conf.js index 61cdf82df32..8e1787de329 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -25,8 +25,14 @@ plugins.push( // this plugin must be last so it can be easily removed for karma new webpack.optimize.CommonsChunkPlugin({ name: 'prebid', filename: 'prebid-core.js', - minChunks: function(module, count) { - return !(count < 2 || neverBundle.indexOf(path.basename(module.resource)) !== -1) + minChunks: function(module) { + return ( + ( + module.context && module.context === path.resolve('./src') && + !(module.resource && neverBundle.some(name => module.resource.includes(name))) + ) || + module.resource && module.resource.includes(path.resolve('./node_modules/core-js')) + ); } }) );