diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 2934a30fb47..45ca30a7a3d 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -128,6 +128,10 @@ Follow steps above for general review process. In addition: - Consider whether the kind of data the module is obtaining could have privacy implications. If so, make sure they're utilizing the `consent` data passed to them. - Make sure there's a docs pull request +### Reviewing changes to the `debugging` module + +The debugging module cannot import from core in the same way that other modules can. See this [warning](https://github.com/prebid/Prebid.js/blob/master/modules/debugging/WARNING.md) for more details. + ## Ticket Coordinator Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. Every Monday morning a reminder is sent to the prebid-js slack channel with a link to the spreadsheet. If you're on rotation, please check that list each Monday to see if you're on-duty. diff --git a/babelConfig.js b/babelConfig.js index c1ddc11b689..785c6171c42 100644 --- a/babelConfig.js +++ b/babelConfig.js @@ -9,7 +9,7 @@ function useLocal(module) { }) } -module.exports = function (test = false) { +module.exports = function (options = {}) { return { 'presets': [ [ @@ -18,12 +18,12 @@ module.exports = function (test = false) { 'useBuiltIns': 'entry', 'corejs': '3.13.0', // a lot of tests use sinon.stub & others that stopped working on ES6 modules with webpack 5 - 'modules': test ? 'commonjs' : 'auto', + 'modules': options.test ? 'commonjs' : 'auto', } ] ], 'plugins': [ - path.resolve(__dirname, './plugins/pbjsGlobals.js'), + [path.resolve(__dirname, './plugins/pbjsGlobals.js'), options], useLocal('babel-plugin-transform-object-assign'), ], } diff --git a/gulpfile.js b/gulpfile.js index adf052ca240..6fcaa59da18 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -118,6 +118,15 @@ function makeDevpackPkg() { devtool: 'source-map', mode: 'development' }) + + const babelConfig = require('./babelConfig.js')({prebidDistUrlBase: '/build/dev/'}); + + // update babel config to set local dist url + cloned.module.rules + .flatMap((rule) => rule.use) + .filter((use) => use.loader === 'babel-loader') + .forEach((use) => use.options = Object.assign({}, use.options, babelConfig)); + var externalModules = helpers.getArgModules(); const analyticsSources = helpers.getAnalyticsSources(); diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 83e1ddb9721..2a174530352 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -22,7 +22,7 @@ function newWebpackConfig(codeCoverage) { .flatMap((r) => r.use) .filter((use) => use.loader === 'babel-loader') .forEach((use) => { - use.options = babelConfig(true); + use.options = babelConfig({test: true}); }); if (codeCoverage) { diff --git a/modules/debugging/WARNING.md b/modules/debugging/WARNING.md new file mode 100644 index 00000000000..109d6db7704 --- /dev/null +++ b/modules/debugging/WARNING.md @@ -0,0 +1,9 @@ +## Warning + +This module is also packaged as a "standalone" .js file and loaded dynamically by prebid-core when debugging configuration is passed to `setConfig` or loaded from session storage. + +"Standalone" means that it does not have a compile-time dependency on `prebid-core.js` and can therefore work even if it was not built together with it (as would be the case when Prebid is pulled from npm). + +Because of this, **this module cannot freely import symbols from core**: anything that depends on Prebid global state (which includes, but is not limited to, `config`, `auctionManager`, `adapterManager`, etc) would *not* work as expected. + +Imports must be limited to logic that is stateless and free of side effects; symbols from `utils.js` are mostly OK, with the notable exception of logging functions (which have a dependency on `config`). diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 2a179641424..3c214f385d2 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -3,10 +3,8 @@ import { deepClone, deepEqual, delayExecution, - prefixLog, mergeDeep } from '../../src/utils.js'; -const { logMessage, logWarn, logError } = prefixLog('DEBUG:'); /** * @typedef {Number|String|boolean|null|undefined} Scalar @@ -14,6 +12,7 @@ const { logMessage, logWarn, logError } = prefixLog('DEBUG:'); export function BidInterceptor(opts = {}) { ({setTimeout: this.setTimeout = window.setTimeout.bind(window)} = opts); + this.logger = opts.logger; this.rules = []; } @@ -22,10 +21,10 @@ Object.assign(BidInterceptor.prototype, { delay: 0 }, serializeConfig(ruleDefs) { - function isSerializable(ruleDef, i) { + const isSerializable = (ruleDef, i) => { const serializable = deepEqual(ruleDef, JSON.parse(JSON.stringify(ruleDef)), {checkTypes: true}); if (!serializable && !deepAccess(ruleDef, 'options.suppressWarnings')) { - logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); + this.logger.logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); } return serializable; } @@ -79,7 +78,7 @@ Object.assign(BidInterceptor.prototype, { return matchDef; } if (typeof matchDef !== 'object') { - logError(`Invalid 'when' definition for debug bid interceptor (in rule #${ruleNo})`); + this.logger.logError(`Invalid 'when' definition for debug bid interceptor (in rule #${ruleNo})`); return () => false; } function matches(candidate, {ref = matchDef, args = []}) { @@ -119,7 +118,7 @@ Object.assign(BidInterceptor.prototype, { if (typeof replDef === 'function') { replFn = ({args}) => replDef(...args); } else if (typeof replDef !== 'object') { - logError(`Invalid 'then' definition for debug bid interceptor (in rule #${ruleNo})`); + this.logger.logError(`Invalid 'then' definition for debug bid interceptor (in rule #${ruleNo})`); replFn = () => ({}); } else { replFn = ({args, ref = replDef}) => { @@ -213,7 +212,7 @@ Object.assign(BidInterceptor.prototype, { matches.forEach((match) => { const mockResponse = match.rule.replace(match.bid, bidRequest); const delay = match.rule.options.delay; - logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) + this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) this.setTimeout(() => { addBid(mockResponse, match.bid); callDone(); diff --git a/modules/debugging/debugging.js b/modules/debugging/debugging.js new file mode 100644 index 00000000000..bf16eaf85a6 --- /dev/null +++ b/modules/debugging/debugging.js @@ -0,0 +1,109 @@ +import {deepClone, delayExecution} from '../../src/utils.js'; +import {BidInterceptor} from './bidInterceptor.js'; +import {makePbsInterceptor} from './pbsInterceptor.js'; +import {addHooks, removeHooks} from './legacy.js'; + +const interceptorHooks = []; +let bidInterceptor; +let enabled = false; + +function enableDebugging(debugConfig, {fromSession = false, config, hook, logger}) { + config.setConfig({debug: true}); + bidInterceptor.updateConfig(debugConfig); + resetHooks(true); + // also enable "legacy" overrides + removeHooks({hook}); + addHooks(debugConfig, {hook, logger}); + if (!enabled) { + enabled = true; + logger.logMessage(`Debug overrides enabled${fromSession ? ' from session' : ''}`); + } +} + +export function disableDebugging({hook, logger}) { + bidInterceptor.updateConfig(({})); + resetHooks(false); + // also disable "legacy" overrides + removeHooks({hook}); + if (enabled) { + enabled = false; + logger.logMessage('Debug overrides disabled'); + } +} + +function saveDebuggingConfig(debugConfig, {sessionStorage = window.sessionStorage, DEBUG_KEY} = {}) { + if (!debugConfig.enabled) { + try { + sessionStorage.removeItem(DEBUG_KEY); + } catch (e) { + } + } else { + if (debugConfig.intercept) { + debugConfig = deepClone(debugConfig); + debugConfig.intercept = bidInterceptor.serializeConfig(debugConfig.intercept); + } + try { + sessionStorage.setItem(DEBUG_KEY, JSON.stringify(debugConfig)); + } catch (e) { + } + } +} + +export function getConfig(debugging, {sessionStorage = window.sessionStorage, DEBUG_KEY, config, hook, logger} = {}) { + if (debugging == null) return; + saveDebuggingConfig(debugging, {sessionStorage, DEBUG_KEY}); + if (!debugging.enabled) { + disableDebugging({hook, logger}); + } else { + enableDebugging(debugging, {config, hook, logger}); + } +} + +export function sessionLoader({DEBUG_KEY, storage, config, hook, logger}) { + let overrides; + try { + storage = storage || window.sessionStorage; + overrides = JSON.parse(storage.getItem(DEBUG_KEY)); + } catch (e) { + } + if (overrides) { + enableDebugging(overrides, {fromSession: true, config, hook, logger}); + } +} + +function resetHooks(enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().getHooks({hook: interceptor}).remove(); + }); + if (enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().before(interceptor); + }); + } +} + +function registerBidInterceptor(getHookFn, interceptor) { + const interceptBids = (...args) => bidInterceptor.intercept(...args); + interceptorHooks.push([getHookFn, function (next, ...args) { + interceptor(next, interceptBids, ...args); + }]); +} + +export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { + const done = delayExecution(cbs.onCompletion, 2); + ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); + if (bids.length === 0) { + done(); + } else { + next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); + } +} + +export function install({DEBUG_KEY, config, hook, createBid, logger}) { + bidInterceptor = new BidInterceptor({logger}); + const pbsBidInterceptor = makePbsInterceptor({createBid}); + registerBidInterceptor(() => hook.get('processBidderRequests'), bidderBidInterceptor); + registerBidInterceptor(() => hook.get('processPBSRequest'), pbsBidInterceptor); + sessionLoader({DEBUG_KEY, config, hook, logger}); + config.getConfig('debugging', ({debugging}) => getConfig(debugging, {DEBUG_KEY, config, hook, logger}), {init: true}); +} diff --git a/modules/debugging/index.js b/modules/debugging/index.js index 72692c3fc98..c529a622267 100644 --- a/modules/debugging/index.js +++ b/modules/debugging/index.js @@ -1,62 +1,7 @@ -import {deepClone, delayExecution} from '../../src/utils.js'; -import {processBidderRequests} from '../../src/adapters/bidderFactory.js'; -import {BidInterceptor} from './bidInterceptor.js'; +import {config} from '../../src/config.js'; import {hook} from '../../src/hook.js'; -import {pbsBidInterceptor} from './pbsInterceptor.js'; -import { - onDisableOverrides, - onEnableOverrides, - saveDebuggingConfig -} from '../../src/debugging.js'; +import {install} from './debugging.js'; +import {prefixLog} from '../../src/utils.js'; +import {createBid} from '../../src/bidfactory.js'; -const interceptorHooks = []; -const bidInterceptor = new BidInterceptor(); - -saveDebuggingConfig.before(function (next, debugConfig, ...args) { - if (debugConfig.intercept) { - debugConfig = deepClone(debugConfig); - debugConfig.intercept = bidInterceptor.serializeConfig(debugConfig.intercept); - } - next(debugConfig, ...args); -}); - -function resetHooks(enable) { - interceptorHooks.forEach(([getHookFn, interceptor]) => { - getHookFn().getHooks({hook: interceptor}).remove(); - }); - if (enable) { - interceptorHooks.forEach(([getHookFn, interceptor]) => { - getHookFn().before(interceptor); - }) - } -} - -onEnableOverrides.push((overrides) => { - bidInterceptor.updateConfig(overrides); - resetHooks(true); -}); - -onDisableOverrides.push(() => { - bidInterceptor.updateConfig({}); - resetHooks(false); -}) - -function registerBidInterceptor(getHookFn, interceptor) { - const interceptBids = (...args) => bidInterceptor.intercept(...args); - interceptorHooks.push([getHookFn, function (next, ...args) { - interceptor(next, interceptBids, ...args) - }]); -} - -export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { - const done = delayExecution(cbs.onCompletion, 2); - ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); - if (bids.length === 0) { - done(); - } else { - next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); - } -} - -registerBidInterceptor(() => processBidderRequests, bidderBidInterceptor); -registerBidInterceptor(() => hook.get('processPBSRequest'), pbsBidInterceptor); +install({config, hook, createBid, logger: prefixLog('DEBUG:')}); diff --git a/modules/debugging/legacy.js b/modules/debugging/legacy.js new file mode 100644 index 00000000000..15b05cded64 --- /dev/null +++ b/modules/debugging/legacy.js @@ -0,0 +1,100 @@ +export let addBidResponseBound; +export let addBidderRequestsBound; + +export function addHooks(overrides, {hook, logger}) { + addBidResponseBound = addBidResponseHook.bind({overrides, logger}); + hook.get('addBidResponse').before(addBidResponseBound, 5); + + addBidderRequestsBound = addBidderRequestsHook.bind({overrides, logger}); + hook.get('addBidderRequests').before(addBidderRequestsBound, 5); +} + +export function removeHooks({hook}) { + hook.get('addBidResponse').getHooks({hook: addBidResponseBound}).remove(); + hook.get('addBidderRequests').getHooks({hook: addBidderRequestsBound}).remove(); +} + +/** + * @param {{bidder:string, adUnitCode:string}} overrideObj + * @param {string} bidderCode + * @param {string} adUnitCode + * @returns {boolean} + */ +export function bidExcluded(overrideObj, bidderCode, adUnitCode) { + if (overrideObj.bidder && overrideObj.bidder !== bidderCode) { + return true; + } + if (overrideObj.adUnitCode && overrideObj.adUnitCode !== adUnitCode) { + return true; + } + return false; +} + +/** + * @param {string[]} bidders + * @param {string} bidderCode + * @returns {boolean} + */ +export function bidderExcluded(bidders, bidderCode) { + return (Array.isArray(bidders) && bidders.indexOf(bidderCode) === -1); +} + +/** + * @param {Object} overrideObj + * @param {Object} bidObj + * @param {Object} bidType + * @returns {Object} bidObj with overridden properties + */ +export function applyBidOverrides(overrideObj, bidObj, bidType, logger) { + return Object.keys(overrideObj).filter(key => (['adUnitCode', 'bidder'].indexOf(key) === -1)).reduce(function(result, key) { + logger.logMessage(`bidder overrides changed '${result.adUnitCode}/${result.bidderCode}' ${bidType}.${key} from '${result[key]}.js' to '${overrideObj[key]}'`); + result[key] = overrideObj[key]; + result.isDebug = true; + return result; + }, bidObj); +} + +export function addBidResponseHook(next, adUnitCode, bid) { + const {overrides, logger} = this; + + if (bidderExcluded(overrides.bidders, bid.bidderCode)) { + logger.logWarn(`bidder '${bid.bidderCode}' excluded from auction by bidder overrides`); + return; + } + + if (Array.isArray(overrides.bids)) { + overrides.bids.forEach(function(overrideBid) { + if (!bidExcluded(overrideBid, bid.bidderCode, adUnitCode)) { + applyBidOverrides(overrideBid, bid, 'bidder', logger); + } + }); + } + + next(adUnitCode, bid); +} + +export function addBidderRequestsHook(next, bidderRequests) { + const {overrides, logger} = this; + + const includedBidderRequests = bidderRequests.filter(function (bidderRequest) { + if (bidderExcluded(overrides.bidders, bidderRequest.bidderCode)) { + logger.logWarn(`bidRequest '${bidderRequest.bidderCode}' excluded from auction by bidder overrides`); + return false; + } + return true; + }); + + if (Array.isArray(overrides.bidRequests)) { + includedBidderRequests.forEach(function(bidderRequest) { + overrides.bidRequests.forEach(function(overrideBid) { + bidderRequest.bids.forEach(function(bid) { + if (!bidExcluded(overrideBid, bidderRequest.bidderCode, bid.adUnitCode)) { + applyBidOverrides(overrideBid, bid, 'bidRequest', logger); + } + }); + }); + }); + } + + next(includedBidderRequests); +} diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index c8de1ed9753..1ca13eb4927 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -1,38 +1,39 @@ import {deepClone, delayExecution} from '../../src/utils.js'; -import {createBid} from '../../src/bidfactory.js'; -import {default as CONSTANTS} from '../../src/constants.json'; +import CONSTANTS from '../../src/constants.json'; -export function pbsBidInterceptor (next, interceptBids, s2sBidRequest, bidRequests, ajax, { - onResponse, - onError, - onBid -}) { - let responseArgs; - const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) - function signalResponse(...args) { - responseArgs = args; - done(); - } - function addBid(bid, bidRequest) { - onBid({ - adUnit: bidRequest.adUnitCode, - bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid) - }) - } - bidRequests = bidRequests - .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) - .filter((req) => req.bids.length > 0) +export function makePbsInterceptor({createBid}) { + return function pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, { + onResponse, + onError, + onBid + }) { + let responseArgs; + const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) + function signalResponse(...args) { + responseArgs = args; + done(); + } + function addBid(bid, bidRequest) { + onBid({ + adUnit: bidRequest.adUnitCode, + bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid) + }) + } + bidRequests = bidRequests + .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) + .filter((req) => req.bids.length > 0) - if (bidRequests.length > 0) { - const bidIds = new Set(); - bidRequests.forEach((req) => req.bids.forEach((bid) => bidIds.add(bid.bidId))); - s2sBidRequest = deepClone(s2sBidRequest); - s2sBidRequest.ad_units.forEach((unit) => { - unit.bids = unit.bids.filter((bid) => bidIds.has(bid.bid_id)); - }) - s2sBidRequest.ad_units = s2sBidRequest.ad_units.filter((unit) => unit.bids.length > 0); - next(s2sBidRequest, bidRequests, ajax, {onResponse: signalResponse, onError, onBid}); - } else { - signalResponse(true, []); + if (bidRequests.length > 0) { + const bidIds = new Set(); + bidRequests.forEach((req) => req.bids.forEach((bid) => bidIds.add(bid.bidId))); + s2sBidRequest = deepClone(s2sBidRequest); + s2sBidRequest.ad_units.forEach((unit) => { + unit.bids = unit.bids.filter((bid) => bidIds.has(bid.bid_id)); + }) + s2sBidRequest.ad_units = s2sBidRequest.ad_units.filter((unit) => unit.bids.length > 0); + next(s2sBidRequest, bidRequests, ajax, {onResponse: signalResponse, onError, onBid}); + } else { + signalResponse(true, []); + } } } diff --git a/modules/debugging/standalone.js b/modules/debugging/standalone.js new file mode 100644 index 00000000000..b3b539f5aa2 --- /dev/null +++ b/modules/debugging/standalone.js @@ -0,0 +1,7 @@ +import {install} from './debugging.js'; + +window._pbjsGlobals.forEach((name) => { + if (window[name] && window[name]._installDebugging === true) { + window[name]._installDebugging = install; + } +}) diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index 79dafd1e8b2..f611db1444c 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -3,12 +3,21 @@ let t = require('@babel/core').types; let prebid = require('../package.json'); const path = require('path'); +function getNpmVersion(version) { + try { + return /^(.*?)(-pre)?$/.exec(version)[1]; + } catch (e) { + return 'latest'; + } +} + module.exports = function(api, options) { const pbGlobal = options.globalVarName || prebid.globalVarName; let replace = { '$prebid.version$': prebid.version, '$$PREBID_GLOBAL$$': pbGlobal, - '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}` + '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`, + '$$PREBID_DIST_URL_BASE$$': options.prebidDistUrlBase || `https://cdn.jsdelivr.net/npm/prebid.js@${getNpmVersion(prebid.version)}/dist/` }; let identifierToStringLiteral = [ diff --git a/src/adloader.js b/src/adloader.js index b167079d488..b0b24718c6c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -4,6 +4,7 @@ import { logError, logWarn, insertElement } from './utils.js'; const _requestCache = new WeakMap(); // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ + 'debugging', 'adloox', 'criteo', 'outstream', diff --git a/src/config.js b/src/config.js index 2403719557f..a50edc09221 100644 --- a/src/config.js +++ b/src/config.js @@ -414,6 +414,8 @@ export function newConfig() { * updates when specific properties are updated by passing a topic string as * the first parameter. * + * If `options.init` is true, the listener will be immediately called with the current options. + * * Returns an `unsubscribe` function for removing the subscriber from the * set of listeners * @@ -427,8 +429,9 @@ export function newConfig() { * // unsubscribe * const unsubscribe = subscribe(...); * unsubscribe(); // no longer listening + * */ - function subscribe(topic, listener) { + function subscribe(topic, listener, options = {}) { let callback = listener; if (typeof topic !== 'string') { @@ -436,6 +439,7 @@ export function newConfig() { // meaning it gets called for any config change callback = topic; topic = ALL_TOPICS; + options = listener || {}; } if (typeof callback !== 'function') { @@ -446,6 +450,15 @@ export function newConfig() { const nl = { topic, callback }; listeners.push(nl); + if (options.init) { + if (topic === ALL_TOPICS) { + callback(getConfig()) + } else { + // eslint-disable-next-line standard/no-callback-literal + callback({[topic]: getConfig(topic)}); + } + } + // save and call this function to remove the listener return function unsubscribe() { listeners.splice(listeners.indexOf(nl), 1); diff --git a/src/debugging.js b/src/debugging.js index 810cf4b432a..aae92197096 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -1,165 +1,84 @@ import {config} from './config.js'; -import {addBidderRequests, addBidResponse} from './auction.js'; -import {hook} from './hook.js'; -import {prefixLog} from './utils.js'; +import {getHook, hook} from './hook.js'; +import {getGlobal} from './prebidGlobal.js'; +import {logMessage, prefixLog} from './utils.js'; +import {createBid} from './bidfactory.js'; +import {loadExternalScript} from './adloader.js'; -const {logWarn, logMessage} = prefixLog('DEBUG:'); +export const DEBUG_KEY = '__$$PREBID_GLOBAL$$_debugging__'; -const OVERRIDE_KEY = '$$PREBID_GLOBAL$$:debugging'; - -export let addBidResponseBound; -export let addBidderRequestsBound; - -export const onEnableOverrides = [ - (overrides) => { - removeHooks(); - addHooks(overrides); - } -]; -export const onDisableOverrides = [ - removeHooks -]; - -function addHooks(overrides) { - addBidResponseBound = addBidResponseHook.bind(overrides); - addBidResponse.before(addBidResponseBound, 5); - - addBidderRequestsBound = addBidderRequestsHook.bind(overrides); - addBidderRequests.before(addBidderRequestsBound, 5); +function isDebuggingInstalled() { + return getGlobal().installedModules.includes('debugging'); } -function removeHooks() { - addBidResponse.getHooks({hook: addBidResponseBound}).remove(); - addBidderRequests.getHooks({hook: addBidderRequestsBound}).remove(); -} - -export function enableOverrides(overrides, fromSession = false) { - config.setConfig({'debug': true}); - onEnableOverrides.forEach((fn) => fn(overrides)); - logMessage(`bidder overrides enabled${fromSession ? ' from session' : ''}`); -} - -export function disableOverrides() { - onDisableOverrides.forEach((fn) => fn()); - logMessage('bidder overrides disabled'); +function loadScript(url) { + return new Promise((resolve) => { + loadExternalScript(url, 'debugging', resolve); + }); } -/** - * @param {{bidder:string, adUnitCode:string}} overrideObj - * @param {string} bidderCode - * @param {string} adUnitCode - * @returns {boolean} - */ -export function bidExcluded(overrideObj, bidderCode, adUnitCode) { - if (overrideObj.bidder && overrideObj.bidder !== bidderCode) { - return true; - } - if (overrideObj.adUnitCode && overrideObj.adUnitCode !== adUnitCode) { - return true; +export function debuggingModuleLoader({alreadyInstalled = isDebuggingInstalled, script = loadScript} = {}) { + let loading = null; + return function () { + if (loading == null) { + loading = new Promise((resolve, reject) => { + setTimeout(() => { + if (alreadyInstalled()) { + resolve(); + } else { + const url = '$$PREBID_DIST_URL_BASE$$debugging-standalone.js'; + logMessage(`Debugging module not installed, loading it from "${url}"...`); + getGlobal()._installDebugging = true; + script(url).then(() => { + getGlobal()._installDebugging({DEBUG_KEY, hook, config, createBid, logger: prefixLog('DEBUG:')}); + }).then(resolve, reject); + } + }); + }) + } + return loading; } - return false; } -/** - * @param {string[]} bidders - * @param {string} bidderCode - * @returns {boolean} - */ -export function bidderExcluded(bidders, bidderCode) { - return (Array.isArray(bidders) && bidders.indexOf(bidderCode) === -1); -} - -/** - * @param {Object} overrideObj - * @param {Object} bidObj - * @param {Object} bidType - * @returns {Object} bidObj with overridden properties - */ -export function applyBidOverrides(overrideObj, bidObj, bidType) { - return Object.keys(overrideObj).filter(key => (['adUnitCode', 'bidder'].indexOf(key) === -1)).reduce(function(result, key) { - logMessage(`bidder overrides changed '${result.adUnitCode}/${result.bidderCode}' ${bidType}.${key} from '${result[key]}.js' to '${overrideObj[key]}'`); - result[key] = overrideObj[key]; - result.isDebug = true; - return result; - }, bidObj); -} - -export function addBidResponseHook(next, adUnitCode, bid) { - const overrides = this; - - if (bidderExcluded(overrides.bidders, bid.bidderCode)) { - logWarn(`bidder '${bid.bidderCode}' excluded from auction by bidder overrides`); - return; +export function debuggingControls({load = debuggingModuleLoader(), hook = getHook('requestBids')} = {}) { + let promise = null; + let enabled = false; + function waitForDebugging(next, ...args) { + return (promise || Promise.resolve()).then(() => next.apply(this, args)) } - - if (Array.isArray(overrides.bids)) { - overrides.bids.forEach(function(overrideBid) { - if (!bidExcluded(overrideBid, bid.bidderCode, adUnitCode)) { - applyBidOverrides(overrideBid, bid, 'bidder'); - } - }); - } - - next(adUnitCode, bid); -} - -export function addBidderRequestsHook(next, bidderRequests) { - const overrides = this; - - const includedBidderRequests = bidderRequests.filter(function (bidderRequest) { - if (bidderExcluded(overrides.bidders, bidderRequest.bidderCode)) { - logWarn(`bidRequest '${bidderRequest.bidderCode}' excluded from auction by bidder overrides`); - return false; + function enable() { + if (!enabled) { + promise = load(); + // set debugging to high priority so that it has the opportunity to mess with most things + hook.before(waitForDebugging, 99); + enabled = true; } - return true; - }); - - if (Array.isArray(overrides.bidRequests)) { - includedBidderRequests.forEach(function(bidderRequest) { - overrides.bidRequests.forEach(function(overrideBid) { - bidderRequest.bids.forEach(function(bid) { - if (!bidExcluded(overrideBid, bidderRequest.bidderCode, bid.adUnitCode)) { - applyBidOverrides(overrideBid, bid, 'bidRequest'); - } - }); - }); - }); } - - next(includedBidderRequests); -} - -export const saveDebuggingConfig = hook('sync', function (debugConfig, {sessionStorage = window.sessionStorage} = {}) { - if (!debugConfig.enabled) { - try { - sessionStorage.removeItem(OVERRIDE_KEY); - } catch (e) {} - } else { - try { - sessionStorage.setItem(OVERRIDE_KEY, JSON.stringify(debugConfig)); - } catch (e) {} + function disable() { + hook.getHooks({hook: waitForDebugging}).remove(); + enabled = false; } -}); - -export function getConfig(debugging, {sessionStorage = window.sessionStorage} = {}) { - saveDebuggingConfig(debugging, {sessionStorage}); - if (!debugging.enabled) { - disableOverrides(); - } else { - enableOverrides(debugging); + function reset() { + promise = null; + disable(); } + return {enable, disable, reset}; } -config.getConfig('debugging', ({debugging}) => getConfig(debugging)); +const ctl = debuggingControls(); +export const reset = ctl.reset; -export function sessionLoader(storage) { - let overrides; +export function loadSession({storage = window.sessionStorage, debugging = ctl} = {}) { + let config = null; try { - storage = storage || window.sessionStorage; - overrides = JSON.parse(storage.getItem(OVERRIDE_KEY)); - } catch (e) { - } - if (overrides) { - enableOverrides(overrides, true); + config = storage.getItem(DEBUG_KEY); + } catch (e) {} + if (config != null) { + // just make sure the module runs; it will take care of parsing the config (and disabling itself if necessary) + debugging.enable(); } } + +config.getConfig('debugging', function ({debugging}) { + debugging?.enabled ? ctl.enable() : ctl.disable(); +}); diff --git a/src/prebid.js b/src/prebid.js index 367695ccf9f..f6df1376708 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -13,7 +13,7 @@ import { config } from './config.js'; import { auctionManager } from './auctionManager.js'; import { filters, targeting } from './targeting.js'; import { hook } from './hook.js'; -import { sessionLoader } from './debugging.js'; +import { loadSession } from './debugging.js'; import {includes} from './polyfill.js'; import { adunitCounter } from './adUnits.js'; import { executeRenderer, isRendererRequired } from './Renderer.js'; @@ -36,7 +36,7 @@ const eventValidators = { }; // initialize existing debugging sessions if present -sessionLoader(); +loadSession(); /* Public vars */ $$PREBID_GLOBAL$$.bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; @@ -581,7 +581,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) } return startAuction({bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ortb2Fragments}); -}); +}, 'requestBids'); export const startAuction = hook('async', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ortb2Fragments } = {}) { const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 2618d1d9c10..48b915c77f6 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -18,7 +18,7 @@ import {find} from 'src/polyfill.js'; import { server } from 'test/mocks/xhr.js'; import {hook} from '../../src/hook.js'; import {auctionManager} from '../../src/auctionManager.js'; -import 'src/debugging.js' // some tests look for debugging side effects +import 'modules/debugging/index.js' // some tests look for debugging side effects import {AuctionIndex} from '../../src/auctionIndex.js'; import {expect} from 'chai'; import {synchronizePromise} from '../helpers/syncPromise.js'; diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 5fead2d97e8..294fce2fa9b 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -106,6 +106,20 @@ describe('config API', function () { sinon.assert.calledOnce(wildcard); }); + it('getConfig subscribers are called immediately if passed {init: true}', () => { + const listener = sinon.spy(); + setConfig({foo: 'bar'}); + getConfig('foo', listener, {init: true}); + sinon.assert.calledWith(listener, {foo: 'bar'}); + }); + + it('getConfig subscribers with no topic are called immediately if passed {init: true}', () => { + const listener = sinon.spy(); + setConfig({foo: 'bar'}); + getConfig(listener, {init: true}); + sinon.assert.calledWith(listener, sinon.match({foo: 'bar'})); + }); + it('sets and gets arbitrary configuration properties', function () { setConfig({ baz: 'qux' }); expect(getConfig('baz')).to.equal('qux'); diff --git a/test/spec/debugging_spec.js b/test/spec/debugging_spec.js index b0f78243e4a..34eafbda61a 100644 --- a/test/spec/debugging_spec.js +++ b/test/spec/debugging_spec.js @@ -1,216 +1,93 @@ - -import { expect } from 'chai'; -import { sessionLoader, addBidResponseHook, addBidderRequestsHook, getConfig, disableOverrides, addBidResponseBound, addBidderRequestsBound } from 'src/debugging.js'; -import { addBidResponse, addBidderRequests } from 'src/auction.js'; -import { config } from 'src/config.js'; -import {hook} from '../../src/hook.js'; - -describe('bid overrides', function () { - let sandbox; - - before(() => { - hook.ready(); - }); - - beforeEach(function () { - sandbox = sinon.sandbox.create(); +import {ready, loadSession, getConfig, reset, debuggingModuleLoader, debuggingControls} from '../../src/debugging.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; +import {promiseControls} from '../../src/utils/promise.js'; +import funHooks from 'fun-hooks/no-eval/index.js'; + +describe('Debugging', () => { + beforeEach(() => { + reset(); }); - afterEach(function () { - window.sessionStorage.clear(); - config.resetConfig(); - sandbox.restore(); + after(() => { + reset(); }); - describe('initialization', function () { - beforeEach(function () { - sandbox.stub(config, 'setConfig'); - }); - - afterEach(function () { - disableOverrides(); - }); - - it('should happen when enabled with setConfig', function () { - getConfig({ - enabled: true + describe('module loader', () => { + let script, scriptResult, alreadyInstalled, loader; + beforeEach(() => { + script = sinon.stub().callsFake(() => { + getGlobal()._installDebugging = sinon.stub(); + return scriptResult; }); - - expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); - expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); + alreadyInstalled = sinon.stub(); + loader = debuggingModuleLoader({alreadyInstalled, script}); }); - it('should happen when configuration found in sessionStorage', function () { - sessionLoader({ - getItem: () => ('{"enabled": true}') - }); - expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); - expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); - }); - - it('should not throw if sessionStorage is inaccessible', function () { - expect(() => { - sessionLoader({ - getItem() { - throw new Error('test'); - } - }); - }).not.to.throw(); - }); - }); - - describe('bidResponse hook', function () { - let mockBids; - let bids; - - beforeEach(function () { - let baseBid = { - 'bidderCode': 'rubicon', - 'width': 970, - 'height': 250, - 'statusMessage': 'Bid available', - 'mediaType': 'banner', - 'source': 'client', - 'currency': 'USD', - 'cpm': 0.5, - 'ttl': 300, - 'netRevenue': false, - 'adUnitCode': '/19968336/header-bid-tag-0' - }; - mockBids = []; - mockBids.push(baseBid); - mockBids.push(Object.assign({}, baseBid, { - bidderCode: 'appnexus' - })); - - bids = []; - }); + afterEach(() => { + delete getGlobal()._installDebugging; + }) - function run(overrides) { - mockBids.forEach(bid => { - let next = (adUnitCode, bid) => { - bids.push(bid); - }; - addBidResponseHook.bind(overrides)(next, bid.adUnitCode, bid); + it('should not attempt to load if debugging module is already installed', () => { + alreadyInstalled.returns(true); + return loader().then(() => { + expect(script.called).to.be.false; }); - } - - it('should allow us to exclude bidders', function () { - run({ - enabled: true, - bidders: ['appnexus'] - }); - - expect(bids.length).to.equal(1); - expect(bids[0].bidderCode).to.equal('appnexus'); }); - it('should allow us to override all bids', function () { - run({ - enabled: true, - bids: [{ - cpm: 2 - }] - }); - - expect(bids.length).to.equal(2); - sinon.assert.match(bids[0], { - cpm: 2, - isDebug: true, - }) - sinon.assert.match(bids[1], { - cpm: 2, - isDebug: true, + it('should not attempt to load twice', () => { + alreadyInstalled.returns(false); + scriptResult = Promise.resolve(); + return Promise.all([loader(), loader()]).then(() => { + expect(script.callCount).to.equal(1); }); }); - it('should allow us to override bids by bidder', function () { - run({ - enabled: true, - bids: [{ - bidder: 'rubicon', - cpm: 2 - }] + it('should call _installDebugging after loading', () => { + alreadyInstalled.returns(false); + scriptResult = Promise.resolve(); + return loader().then(() => { + expect(getGlobal()._installDebugging.called).to.be.true; }); - - expect(bids.length).to.equal(2); - sinon.assert.match(bids[0], { - cpm: 2, - isDebug: true - }); - sinon.assert.match(bids[1], { - cpm: 0.5, - isDebug: sinon.match.falsy - }) }); - it('should allow us to override bids by adUnitCode', function () { - mockBids[1].adUnitCode = 'test'; - - run({ - enabled: true, - bids: [{ - adUnitCode: 'test', - cpm: 2 - }] - }); - - expect(bids.length).to.equal(2); - sinon.assert.match(bids[0], { - cpm: 0.5, - isDebug: sinon.match.falsy, - }); - sinon.assert.match(bids[1], { - cpm: 2, - isDebug: true, + it('should not call _installDebugging if load fails', () => { + const error = new Error(); + alreadyInstalled.returns(false); + scriptResult = Promise.reject(error) + return loader().then(() => { + throw new Error('loader should not resolve'); + }).catch((err) => { + expect(err).to.equal(error); + expect(getGlobal()._installDebugging.called).to.be.false; }); }); }); - describe('bidRequests hook', function () { - let mockBidRequests; - let bidderRequests; - - beforeEach(function () { - let baseBidderRequest = { - 'bidderCode': 'rubicon', - 'bids': [{ - 'width': 970, - 'height': 250, - 'statusMessage': 'Bid available', - 'mediaType': 'banner', - 'source': 'client', - 'currency': 'USD', - 'cpm': 0.5, - 'ttl': 300, - 'netRevenue': false, - 'adUnitCode': '/19968336/header-bid-tag-0' - }] - }; - mockBidRequests = []; - mockBidRequests.push(baseBidderRequest); - mockBidRequests.push(Object.assign({}, baseBidderRequest, { - bidderCode: 'appnexus' - })); - - bidderRequests = []; - }); - - function run(overrides) { - let next = (b) => { - bidderRequests = b; - }; - addBidderRequestsHook.bind(overrides)(next, mockBidRequests); - } - - it('should allow us to exclude bidders', function () { - run({ - enabled: true, - bidders: ['appnexus'] + describe('debugging controls', () => { + let debugging, loader, hook, hookRan; + + beforeEach(() => { + loader = promiseControls(); + hookRan = false; + hook = funHooks()('sync', () => { hookRan = true }); + debugging = debuggingControls({load: sinon.stub().returns(loader.promise), hook}); + }) + + it('should delay execution of hook until module is loaded', () => { + debugging.enable(); + hook(); + expect(hookRan).to.be.false; + loader.resolve(); + return loader.promise.then(() => { + expect(hookRan).to.be.true; }); - - expect(bidderRequests.length).to.equal(1); - expect(bidderRequests[0].bidderCode).to.equal('appnexus'); }); + + it('should restore hook behavior when disabled', () => { + debugging.enable(); + debugging.disable(); + hook(); + expect(hookRan).to.be.true; + }) }); }); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index 79866d023e9..fa3b281da76 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -1,13 +1,31 @@ import {expect} from 'chai'; import {BidInterceptor} from '../../../modules/debugging/bidInterceptor.js'; -import {bidderBidInterceptor} from '../../../modules/debugging/index.js'; -import {pbsBidInterceptor} from '../../../modules/debugging/pbsInterceptor.js'; +import { + bidderBidInterceptor, + disableDebugging, + getConfig, + sessionLoader, +} from '../../../modules/debugging/debugging.js'; +import '../../../modules/debugging/index.js'; +import {makePbsInterceptor} from '../../../modules/debugging/pbsInterceptor.js'; +import {config} from '../../../src/config.js'; +import {hook} from '../../../src/hook.js'; +import { + addBidderRequestsBound, + addBidderRequestsHook, + addBidResponseBound, + addBidResponseHook, +} from '../../../modules/debugging/legacy.js'; + +import {addBidderRequests, addBidResponse} from '../../../src/auction.js'; +import {prefixLog} from '../../../src/utils.js'; +import {createBid} from '../../../src/bidfactory.js'; describe('bid interceptor', () => { let interceptor, mockSetTimeout; beforeEach(() => { mockSetTimeout = sinon.stub().callsFake((fn) => fn()); - interceptor = new BidInterceptor({setTimeout: mockSetTimeout}); + interceptor = new BidInterceptor({setTimeout: mockSetTimeout, logger: prefixLog('TEST')}); }); function setRules(...rules) { @@ -350,6 +368,7 @@ describe('pbsBidInterceptor', () => { interceptResults = [EMPTY_INT_RES, EMPTY_INT_RES]; }); + const pbsBidInterceptor = makePbsInterceptor({createBid}); function callInterceptor() { return pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}); } @@ -449,3 +468,212 @@ describe('pbsBidInterceptor', () => { }); }); }); + +describe('bid overrides', function () { + let sandbox; + const logger = prefixLog('TEST'); + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + window.sessionStorage.clear(); + config.resetConfig(); + sandbox.restore(); + }); + + describe('initialization', function () { + beforeEach(function () { + sandbox.stub(config, 'setConfig'); + }); + + afterEach(function () { + disableDebugging({hook, logger}); + }); + + it('should happen when enabled with setConfig', function () { + getConfig({ + enabled: true + }, {config, hook, logger}); + + expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); + expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); + }); + it('should happen when configuration found in sessionStorage', function () { + sessionLoader({ + storage: {getItem: () => ('{"enabled": true}')}, + config, + hook, + logger + }); + expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); + expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); + }); + + it('should not throw if sessionStorage is inaccessible', function () { + expect(() => { + sessionLoader({ + getItem() { + throw new Error('test'); + } + }); + }).not.to.throw(); + }); + }); + + describe('bidResponse hook', function () { + let mockBids; + let bids; + + beforeEach(function () { + let baseBid = { + 'bidderCode': 'rubicon', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'mediaType': 'banner', + 'source': 'client', + 'currency': 'USD', + 'cpm': 0.5, + 'ttl': 300, + 'netRevenue': false, + 'adUnitCode': '/19968336/header-bid-tag-0' + }; + mockBids = []; + mockBids.push(baseBid); + mockBids.push(Object.assign({}, baseBid, { + bidderCode: 'appnexus' + })); + + bids = []; + }); + + function run(overrides) { + mockBids.forEach(bid => { + let next = (adUnitCode, bid) => { + bids.push(bid); + }; + addBidResponseHook.bind({overrides, logger})(next, bid.adUnitCode, bid); + }); + } + + it('should allow us to exclude bidders', function () { + run({ + enabled: true, + bidders: ['appnexus'] + }); + + expect(bids.length).to.equal(1); + expect(bids[0].bidderCode).to.equal('appnexus'); + }); + + it('should allow us to override all bids', function () { + run({ + enabled: true, + bids: [{ + cpm: 2 + }] + }); + + expect(bids.length).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 2, + isDebug: true, + }); + sinon.assert.match(bids[1], { + cpm: 2, + isDebug: true, + }); + }); + + it('should allow us to override bids by bidder', function () { + run({ + enabled: true, + bids: [{ + bidder: 'rubicon', + cpm: 2 + }] + }); + + expect(bids.length).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 2, + isDebug: true + }); + sinon.assert.match(bids[1], { + cpm: 0.5, + isDebug: sinon.match.falsy + }); + }); + + it('should allow us to override bids by adUnitCode', function () { + mockBids[1].adUnitCode = 'test'; + + run({ + enabled: true, + bids: [{ + adUnitCode: 'test', + cpm: 2 + }] + }); + + expect(bids.length).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 0.5, + isDebug: sinon.match.falsy, + }); + sinon.assert.match(bids[1], { + cpm: 2, + isDebug: true, + }); + }); + }); + + describe('bidRequests hook', function () { + let mockBidRequests; + let bidderRequests; + + beforeEach(function () { + let baseBidderRequest = { + 'bidderCode': 'rubicon', + 'bids': [{ + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'mediaType': 'banner', + 'source': 'client', + 'currency': 'USD', + 'cpm': 0.5, + 'ttl': 300, + 'netRevenue': false, + 'adUnitCode': '/19968336/header-bid-tag-0' + }] + }; + mockBidRequests = []; + mockBidRequests.push(baseBidderRequest); + mockBidRequests.push(Object.assign({}, baseBidderRequest, { + bidderCode: 'appnexus' + })); + + bidderRequests = []; + }); + + function run(overrides) { + let next = (b) => { + bidderRequests = b; + }; + addBidderRequestsHook.bind({overrides, logger})(next, mockBidRequests); + } + + it('should allow us to exclude bidders', function () { + run({ + enabled: true, + bidders: ['appnexus'] + }); + + expect(bidderRequests.length).to.equal(1); + expect(bidderRequests[0].bidderCode).to.equal('appnexus'); + }); + }); +}); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 4ec4f3b841c..fa87d7530cd 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -19,6 +19,7 @@ import {find} from 'src/polyfill.js'; import {synchronizePromise} from '../../helpers/syncPromise.js'; import * as pbjsModule from 'src/prebid.js'; import {hook} from '../../../src/hook.js'; +import {reset as resetDebugging} from '../../../src/debugging.js'; import $$PREBID_GLOBAL$$ from 'src/prebid.js'; var assert = require('chai').assert; @@ -198,6 +199,7 @@ describe('Unit: Prebid Module', function () { before(() => { hook.ready(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); + resetDebugging(); }); beforeEach(function () { diff --git a/webpack.conf.js b/webpack.conf.js index 9d85505d96e..8f0b51d0c28 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -29,6 +29,9 @@ module.exports = { const entry = { 'prebid-core': { import: './src/prebid.js' + }, + 'debugging-standalone': { + import: './modules/debugging/standalone.js' } }; const selectedModules = new Set(helpers.getArgModules());