diff --git a/.circleci/config.yml b/.circleci/config.yml index 784b520ecc1..c11f87b6f59 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ aliases: - &run_endtoend_test name: BrowserStack End to end testing - command: echo "127.0.0.1 test.localhost" | sudo tee -a /etc/hosts && gulp e2e-test --host=test.localhost + command: gulp e2e-test # Download and run BrowserStack local - &setup_browserstack @@ -82,16 +82,6 @@ workflows: commit: jobs: - build - nightly: - triggers: - - schedule: - cron: "0 0 * * *" - filters: - branches: - only: - - master - jobs: - - e2etest experimental: pipelines: true diff --git a/gulpfile.js b/gulpfile.js index 0c4ebc50653..ff49436384b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -32,8 +32,8 @@ var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '*/\n'; var port = 9999; -const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; -const FAKE_SERVER_PORT = 4444; +const INTEG_SERVER_HOST = argv.host ? argv.host : 'localhost'; +const INTEG_SERVER_PORT = 4444; const { spawn } = require('child_process'); // these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules @@ -167,9 +167,9 @@ function gulpBundle(dev) { return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist'))); } -function nodeBundle(modules) { +function nodeBundle(modules, dev = false) { return new Promise((resolve, reject) => { - bundle(false, modules) + bundle(dev, modules) .on('error', (err) => { reject(err); }) @@ -243,41 +243,18 @@ function testTaskMaker(options = {}) { if (options.notest) { done(); } else if (options.e2e) { - let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); - let wdioConf = path.join(__dirname, 'wdio.conf.js'); - let wdioOpts; - - if (options.file) { - wdioOpts = [ - wdioConf, - `--spec`, - `${options.file}` - ] - } else { - wdioOpts = [ - wdioConf - ]; - } - - // run fake-server - const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); - fakeServer.stdout.on('data', (data) => { - console.log(`stdout: ${data}`); - }); - fakeServer.stderr.on('data', (data) => { - console.log(`stderr: ${data}`); - }); - - execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) + const integ = startIntegServer(); + startLocalServer(); + runWebdriver(options) .then(stdout => { // kill fake server - fakeServer.kill('SIGINT'); + integ.kill('SIGINT'); done(); process.exit(0); }) .catch(err => { // kill fake server - fakeServer.kill('SIGINT'); + integ.kill('SIGINT'); done(new Error(`Tests failed with error: ${err}`)); process.exit(1); }); @@ -296,6 +273,26 @@ function testTaskMaker(options = {}) { const test = testTaskMaker(); +function runWebdriver({file}) { + process.env.TEST_SERVER_HOST = argv.host || 'localhost'; + let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); + let wdioConf = path.join(__dirname, 'wdio.conf.js'); + let wdioOpts; + + if (file) { + wdioOpts = [ + wdioConf, + `--spec`, + `${file}` + ] + } else { + wdioOpts = [ + wdioConf + ]; + } + return execa(wdioCmd, wdioOpts, { stdio: 'inherit' }); +} + function newKarmaCallback(done) { return function (exitCode) { if (exitCode) { @@ -335,41 +332,29 @@ function buildPostbid() { .pipe(gulp.dest('build/postbid/')); } -function setupE2e(done) { - if (!argv.host) { - throw new gutil.PluginError({ - plugin: 'E2E test', - message: gutil.colors.red('Host should be defined e.g. ap.localhost, anlocalhost. localhost cannot be used as safari browserstack is not able to connect to localhost') - }); +function startIntegServer(dev = false) { + const args = ['./test/fake-server/index.js', `--port=${INTEG_SERVER_PORT}`, `--host=${INTEG_SERVER_HOST}`]; + if (dev) { + args.push('--dev=true') } - process.env.TEST_SERVER_HOST = argv.host; - if (argv.https) { - process.env.TEST_SERVER_PROTOCOL = argv.https; - } - argv.e2e = true; - done(); -} - -function injectFakeServerEndpoint() { - return gulp.src(['build/dist/*.js']) - .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`)) - .pipe(gulp.dest('build/dist')); -} - -function injectFakeServerEndpointDev() { - return gulp.src(['build/dev/*.js']) - .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`)) - .pipe(gulp.dest('build/dev')); -} - -function startFakeServer() { - const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); - fakeServer.stdout.on('data', (data) => { + const srv = spawn('node', args); + srv.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); - fakeServer.stderr.on('data', (data) => { + srv.stderr.on('data', (data) => { console.log(`stderr: ${data}`); }); + return srv; +} + +function startLocalServer(options = {}) { + connect.server({ + https: argv.https, + port: port, + host: INTEG_SERVER_HOST, + root: './', + livereload: options.livereload + }); } // Watch Task with Live Reload @@ -385,13 +370,7 @@ function watchTaskMaker(options = {}) { 'modules/**/*.js', ].concat(options.alsoWatch)); - connect.server({ - https: argv.https, - port: port, - host: FAKE_SERVER_HOST, - root: './', - livereload: options.livereload - }); + startLocalServer(options); mainWatcher.on('all', options.task()); done(); @@ -427,11 +406,13 @@ gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast))); gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true})))); -gulp.task('serve-fake', gulp.series(clean, gulp.parallel('build-bundle-dev', watch), injectFakeServerEndpointDev, test, startFakeServer)); +gulp.task('serve-e2e', gulp.series(clean, 'build-bundle-prod', gulp.parallel(() => startIntegServer(), startLocalServer))) +gulp.task('serve-e2e-dev', gulp.series(clean, 'build-bundle-dev', gulp.parallel(() => startIntegServer(true), startLocalServer))) gulp.task('default', gulp.series(clean, 'build-bundle-prod')); -gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), injectFakeServerEndpoint, test)); +gulp.task('e2e-test-only', () => runWebdriver({file: argv.file})) +gulp.task('e2e-test', gulp.series(clean, 'build-bundle-prod', testTaskMaker({e2e: true}))); // other tasks gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 44c0c6868cf..40809c59093 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -4,7 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import {logInfo} from '../src/utils.js'; import {find, findIndex} from '../src/polyfill.js'; -// Events used in adomik analytics adapter +// Events used in adomik analytics adapter. const auctionInit = CONSTANTS.EVENTS.AUCTION_INIT; const auctionEnd = CONSTANTS.EVENTS.AUCTION_END; const bidRequested = CONSTANTS.EVENTS.BID_REQUESTED; @@ -30,17 +30,13 @@ let adomikAdapter = Object.assign(adapter({}), break; case bidResponse: - adomikAdapter.bucketEvents.push({ - type: 'response', - event: adomikAdapter.buildBidResponse(args) - }); + adomikAdapter.saveBidResponse(args); break; case bidWon: - adomikAdapter.sendWonEvent({ - id: args.adId, - placementCode: args.adUnitCode - }); + args.id = args.adId; + args.placementCode = args.adUnitCode; + adomikAdapter.sendWonEvent(args); break; case bidRequested: @@ -69,16 +65,28 @@ adomikAdapter.initializeBucketEvents = function() { adomikAdapter.bucketEvents = []; } +adomikAdapter.saveBidResponse = function(args) { + let responseSaved = adomikAdapter.bucketEvents.find((bucketEvent) => + bucketEvent.type == 'response' && bucketEvent.event.id == args.id + ); + if (responseSaved) { return true; } + adomikAdapter.bucketEvents.push({ + type: 'response', + event: adomikAdapter.buildBidResponse(args) + }); +} + adomikAdapter.maxPartLength = function () { return (ua.includes(' MSIE ')) ? 1600 : 60000; }; adomikAdapter.sendTypedEvent = function() { + let [testId, testValue] = adomikAdapter.getKeyValues(); const groupedTypedEvents = adomikAdapter.buildTypedEvents(); const bulkEvents = { - testId: adomikAdapter.currentContext.testId, - testValue: adomikAdapter.currentContext.testValue, + testId: testId, + testValue: testValue, uid: adomikAdapter.currentContext.uid, ahbaid: adomikAdapter.currentContext.id, hostname: window.location.hostname, @@ -114,10 +122,8 @@ adomikAdapter.sendTypedEvent = function() { const stringBulkEvents = JSON.stringify(bulkEvents) logInfo('Events sent to adomik prebid analytic ' + stringBulkEvents); - // Encode object in base64 const encodedBuf = window.btoa(stringBulkEvents); - // Create final url and split it (+endpoint length) const encodedUri = encodeURIComponent(encodedBuf); const maxLength = adomikAdapter.maxPartLength(); const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g')); @@ -130,16 +136,18 @@ adomikAdapter.sendTypedEvent = function() { }; adomikAdapter.sendWonEvent = function (wonEvent) { - let keyValues = { testId: adomikAdapter.currentContext.testId, testValue: adomikAdapter.currentContext.testValue } - wonEvent = {...wonEvent, ...keyValues} - const stringWonEvent = JSON.stringify(wonEvent) + let [testId, testValue] = adomikAdapter.getKeyValues(); + let keyValues = { testId: testId, testValue: testValue }; + let samplingInfo = { sampling: adomikAdapter.currentContext.sampling }; + wonEvent = { ...adomikAdapter.buildBidResponse(wonEvent), ...keyValues, ...samplingInfo }; + + const stringWonEvent = JSON.stringify(wonEvent); logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent); - // Encode object in base64 const encodedBuf = window.btoa(stringWonEvent); const encodedUri = encodeURIComponent(encodedBuf); const img = new Image(1, 1); - img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true` + img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true`; } adomikAdapter.buildBidResponse = function (bid) { @@ -201,33 +209,49 @@ adomikAdapter.buildTypedEvents = function () { return groupedTypedEvents; } -adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; +adomikAdapter.getKeyValues = function () { + let preventTest = sessionStorage.getItem(window.location.hostname + '_NoAdomikTest') + let inScope = sessionStorage.getItem(window.location.hostname + '_AdomikTestInScope') + let keyValues = JSON.parse(sessionStorage.getItem(window.location.hostname + '_AdomikTest')) + let testId; + let testValue; + if (typeof (keyValues) === 'object' && keyValues != undefined && !preventTest && inScope) { + testId = keyValues.testId + testValue = keyValues.testOptionLabel + } + return [testId, testValue] +} -adomikAdapter.enableAnalytics = function (config) { - adomikAdapter.currentContext = {}; - const initOptions = config.options; - - _sampled = typeof config === 'undefined' || - typeof config.sampling === 'undefined' || - Math.random() < parseFloat(config.sampling); - - if (_sampled) { - if (initOptions) { - adomikAdapter.currentContext = { - uid: initOptions.id, - url: initOptions.url, - testId: initOptions.testId, - testValue: initOptions.testValue, - id: '', - timeouted: false, - sampling: config.sampling - } - logInfo('Adomik Analytics enabled with config', initOptions); - adomikAdapter.adapterEnableAnalytics(config); - } - } else { - logInfo('Adomik Analytics ignored for sampling', config.sampling); +adomikAdapter.enable = function(options) { + adomikAdapter.currentContext = { + uid: options.id, + url: options.url, + id: '', + timeouted: false, + sampling: options.sampling } + logInfo('Adomik Analytics enabled with config', options); + adomikAdapter.adapterEnableAnalytics(options); +}; + +adomikAdapter.checkOptions = function(options) { + if (typeof options !== 'undefined') { + if (options.id && options.url) { adomikAdapter.enable(options); } else { logInfo('Adomik Analytics disabled because id and/or url is missing from config', options); } + } else { logInfo('Adomik Analytics disabled because config is missing'); } +}; + +adomikAdapter.checkSampling = function(options) { + _sampled = typeof options === 'undefined' || + typeof options.sampling === 'undefined' || + (options.sampling > 0 && Math.random() < parseFloat(options.sampling)); + if (_sampled) { adomikAdapter.checkOptions(options) } else { logInfo('Adomik Analytics ignored for sampling', options.sampling); } +}; + +adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; + +adomikAdapter.enableAnalytics = function ({ provider, options }) { + logInfo('Adomik Analytics enableAnalytics', provider); + adomikAdapter.checkSampling(options); }; adapterManager.registerAnalyticsAdapter({ diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index a28ab4257df..ac49f7ae32d 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -10,7 +10,7 @@ const BIDDER_CODE = 'adot'; const ADAPTER_VERSION = 'v2.0.0'; const BID_METHOD = 'POST'; const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest'; -const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols']; +const REQUIRED_VIDEO_PARAMS = ['mimes', 'protocols']; const DOMAIN_REGEX = new RegExp('//([^/]*)'); const FIRST_PRICE = 1; const IMP_BUILDER = { banner: buildBanner, video: buildVideo, native: buildNative }; diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 1a0074e668b..6710448f00e 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -488,6 +488,8 @@ function createBid(response, bidRequests) { // retreive video response if present const vast64 = response.Vast || getVideoAd(response); if (vast64) { + bid.width = response.Width; + bid.height = response.Height; bid.vastXml = window.atob(vast64); bid.mediaType = 'video'; } else if (request.Native) { diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 421eb99b4c1..d7b6b7c4020 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -2,22 +2,9 @@ import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn } from '. import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'apacdex'; -const CONFIG = { - 'apacdex': { - 'ENDPOINT': 'https://useast.quantumdex.io/auction/apacdex', - 'USERSYNC': 'https://sync.quantumdex.io/usersync/apacdex' - }, - 'quantumdex': { - 'ENDPOINT': 'https://useast.quantumdex.io/auction/quantumdex', - 'USERSYNC': 'https://sync.quantumdex.io/usersync/quantumdex' - }, - 'valueimpression': { - 'ENDPOINT': 'https://useast.quantumdex.io/auction/adapter', - 'USERSYNC': 'https://sync.quantumdex.io/usersync/adapter' - } -}; +const ENDPOINT = 'https://useast.quantumdex.io/auction/pbjs' +const USERSYNC = 'https://sync.quantumdex.io/usersync/pbjs' -var bidderConfig = CONFIG[BIDDER_CODE]; var bySlotTargetKey = {}; var bySlotSizesCount = {} @@ -56,8 +43,6 @@ export const spec = { let test; let bids = []; - bidderConfig = CONFIG[validBidRequests[0].bidder]; - test = config.getConfig('debug'); validBidRequests.forEach(bidReq => { @@ -156,13 +141,14 @@ export const spec = { transactionId: bid.transactionId, sizes: bid.sizes, bidId: bid.bidId, + adUnitCode: bid.adUnitCode, bidFloor: bid.bidFloor } }); return { method: 'POST', - url: bidderConfig.ENDPOINT, + url: ENDPOINT, data: payload, withCredentials: true, bidderRequests: bids @@ -209,32 +195,47 @@ export const spec = { }); return bidResponses; }, - getUserSyncs: function (syncOptions, serverResponses) { + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; - try { - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: bidderConfig.USERSYNC - }); + if (hasPurpose1Consent(gdprConsent)) { + let params = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + params = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params = `?gdpr_consent=${gdprConsent.consentString}`; + } } - if (serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.pixel) { - serverResponses[0].body.pixel.forEach(px => { - if (px.type === 'image' && syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: px.url - }); - } - if (px.type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: px.url - }); - } - }); + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; } - } catch (e) { } + + try { + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USERSYNC + params + }); + } + if (serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.pixel) { + serverResponses[0].body.pixel.forEach(px => { + if (px.type === 'image' && syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: px.url + params + }); + } + if (px.type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: px.url + params + }); + } + }); + } + } catch (e) { } + } return syncs; } }; @@ -377,4 +378,14 @@ function getBidFloor(bid) { return null; } +function hasPurpose1Consent(gdprConsent) { + let result = true; + if (gdprConsent) { + if (gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) { + result = !!(deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + registerBidder(spec); diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index 2c100bce27b..2744e38e820 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -1,32 +1,30 @@ -import { deepAccess, isFn, logError, getValue, getBidIdParameter, _each, isArray, triggerPixel } from '../src/utils.js'; +import { + deepAccess, + isFn, + logError, + getValue, + getBidIdParameter, + _each, + isArray, + triggerPixel, + formatQS, +} from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'audiencerun'; const BASE_URL = 'https://d.audiencerun.com'; const AUCTION_URL = `${BASE_URL}/prebid`; const TIMEOUT_EVENT_URL = `${BASE_URL}/ps/pbtimeout`; +const ERROR_EVENT_URL = `${BASE_URL}/js_log`; const DEFAULT_CURRENCY = 'USD'; let requestedBids = []; /** - * Gets bidder request referer - * - * @param {Object} bidderRequest - * @return {string} - */ -function getPageUrl(bidderRequest) { - return ( - config.getConfig('pageUrl') || - deepAccess(bidderRequest, 'refererInfo.referer') || - null - ); -} - -/** - * Returns bidfloor through floors module if available + * Returns bidfloor through floors module if available. * * @param {Object} bid * @returns {number} @@ -44,19 +42,53 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (_) { - return 0 + return 0; } } +/** + * Returns the most top page referer. + * + * @returns {string} + */ +function getPageReferer() { + let t, e; + do { + t = t ? t.parent : window; + try { + e = t.document.referrer; + } catch (_) { + break; + } + } while (t !== window.top); + return e; +} + +/** + * Returns bidder request page url. + * + * @param {Object} bidderRequest + * @return {string} + */ +function getPageUrl(bidderRequest) { + return ( + config.getConfig('pageUrl') || + deepAccess(bidderRequest, 'refererInfo.referer') || + getPageReferer() || + null + ); +} + export const spec = { - version: '1.1.0', + version: '1.2.0', code: BIDDER_CODE, + gvlid: 944, supportedMediaTypes: [BANNER], /** * Determines whether or not the given bid request is valid. * - * @param {object} bid The bid to validate. + * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { @@ -95,12 +127,19 @@ export const spec = { const payload = { libVersion: this.version, - referer: getPageUrl(bidderRequest), + pageUrl: config.getConfig('pageUrl'), + pageReferer: getPageReferer(), + referer: deepAccess(bidderRequest, 'refererInfo.referer'), + refererInfo: deepAccess(bidderRequest, 'refererInfo'), currencyCode: config.getConfig('currency.adServerCurrency'), timeout: config.getConfig('bidderTimeout'), bids, }; + payload.uspConsent = deepAccess(bidderRequest, 'uspConsent'); + payload.schain = deepAccess(bidRequests, '0.schain'); + payload.userId = deepAccess(bidRequests, '0.userId') ? createEidsArray(bidRequests[0].userId) : []; + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr = { consent: bidderRequest.gdprConsent.consentString, @@ -117,7 +156,7 @@ export const spec = { return { method: 'POST', - url: AUCTION_URL, + url: deepAccess(bidRequests, '0.params.auctionUrl', AUCTION_URL), data: JSON.stringify(payload), options: { withCredentials: true, @@ -201,7 +240,9 @@ export const spec = { } timeoutData.forEach((bid) => { - const bidOnTimeout = requestedBids.find((requestedBid) => requestedBid.bidId === bid.bidId); + const bidOnTimeout = requestedBids.find( + (requestedBid) => requestedBid.bidId === bid.bidId + ); if (bidOnTimeout) { triggerPixel( @@ -210,6 +251,18 @@ export const spec = { } }); }, + + /** + * Registers bidder specific code, which will execute if the bidder responded with an error. + * @param {{bidderRequest: object}} args An object from which we extract bidderRequest object. + */ + onBidderError: function ({ bidderRequest }) { + const queryString = formatQS({ + message: `Prebid.js: Server call for ${bidderRequest.bidderCode} failed.`, + url: encodeURIComponent(getPageUrl(bidderRequest)), + }); + triggerPixel(`${ERROR_EVENT_URL}/?${queryString}`); + }, }; registerBidder(spec); diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index acf574a3fe2..e4907c15974 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { params.maxd = video.maxd; params.mind = video.mind; params.pos = 'atf'; - ENDPOINT += '&jst=pvc'; + params.jst = 'pvc'; params.codeType = includes(CODE_TYPES, video.codeType) ? video.codeType : 'inpage'; } diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index e3d3c65a4f0..f61d58664ca 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' const BIDDER_CODE = 'cointraffic'; -const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps-pbd.ctengine.io/pb/tmp'; const DEFAULT_CURRENCY = 'EUR'; const ALLOWED_CURRENCIES = [ 'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY', diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index c1b6e31ff2e..fec0d1b6510 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -112,7 +112,6 @@ export const spec = { placementId: bid.params.placement_id, groupId: bid.params.group_id, bidId: bid.bidId, - sizes: bid.mediaTypes[traff].sizes, traffic: traff, eids: [], floor: {} @@ -146,7 +145,12 @@ export const spec = { rtiPartner: 'TDID' }); } + if (traff === BANNER) { + placement.sizes = bid.mediaTypes[BANNER].sizes + } + if (traff === VIDEO) { + placement.sizes = bid.mediaTypes[VIDEO].playerSize; placement.playerSize = bid.mediaTypes[VIDEO].playerSize; placement.minduration = bid.mediaTypes[VIDEO].minduration; placement.maxduration = bid.mediaTypes[VIDEO].maxduration; diff --git a/modules/consentManagement.js b/modules/consentManagement.js index f0355749055..5fbcc0f8ac1 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -36,8 +36,8 @@ const cmpCallMap = { * This function reads the consent string from the config to obtain the consent information of the user. * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP */ -function lookupStaticConsentData({onSuccess}) { - onSuccess(staticConsentData); +function lookupStaticConsentData({onSuccess, onError}) { + processCmpData(staticConsentData, {onSuccess, onError}) } /** @@ -335,8 +335,8 @@ export function requestBidsHook(fn, reqBidsConfigObj) { let height = 1; if (Array.isArray(adUnits) && adUnits.length > 0) { let sizes = getAdUnitSizes(adUnits[0]); - width = sizes[0][0]; - height = sizes[0][1]; + width = sizes?.[0]?.[0] || 1; + height = sizes?.[0]?.[1] || 1; } return function (cb) { diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index e98b41d5c9e..d6bf913b366 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -28,8 +28,8 @@ const uspCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. */ -function lookupStaticConsentData({onSuccess}) { - onSuccess(staticConsentData); +function lookupStaticConsentData({onSuccess, onError}) { + processUspData(staticConsentData, {onSuccess, onError}); } /** diff --git a/modules/currency.js b/modules/currency.js index a59a9880af1..7f88c4b0aeb 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -168,6 +168,8 @@ function initCurrency(url) { } } ); + } else { + ready.done(); } } diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index 5af2384cad9..c8de1ed9753 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -1,6 +1,6 @@ import {deepClone, delayExecution} from '../../src/utils.js'; import {createBid} from '../../src/bidfactory.js'; -import * as CONSTANTS from '../../src/constants.json'; +import {default as CONSTANTS} from '../../src/constants.json'; export function pbsBidInterceptor (next, interceptBids, s2sBidRequest, bidRequests, ajax, { onResponse, diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 884c4f0c067..63332db8725 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -41,6 +41,19 @@ function getBidFloor(bid) { } } +function getUserId(eids, id, source, uidExt) { + if (id) { + var uid = { id }; + if (uidExt) { + uid.ext = uidExt; + } + eids.push({ + source, + uids: [ uid ] + }); + } +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -86,7 +99,12 @@ export const spec = { const placement = { placementId: bid.params.placementId, bidId: bid.bidId, - bidfloor: getBidFloor(bid) + bidfloor: getBidFloor(bid), + eids: [] + } + + if (bid.userId) { + getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); } if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index c570e69e1d3..21206109ee0 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -73,8 +73,6 @@ export const ftrackIdSubmodule = { CCampID: '148556' }; window.D9r = { - DeviceID: true, - SingleDeviceID: true, callback: function(response) { if (response) { storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}_exp`, (new Date(Date.now() + (1000 * 60 * 60 * 24 * LOCAL_STORAGE_EXP_DAYS))).toUTCString()); @@ -88,6 +86,22 @@ export const ftrackIdSubmodule = { } }; + // If config.params.ids does not exist, set defaults + if (!config.params.hasOwnProperty('ids')) { + window.D9r.DeviceID = true; + window.D9r.SingleDeviceID = true; + } else { + if (config.params.ids.hasOwnProperty('device id') && config.params.ids['device id'] === true) { + window.D9r.DeviceID = true; + } + if (config.params.ids.hasOwnProperty('single device id') && config.params.ids['single device id'] === true) { + window.D9r.SingleDeviceID = true; + } + if (config.params.ids.hasOwnProperty('household id') && config.params.ids['household id'] === true) { + window.D9r.HHID = true; + } + } + if (config.params && config.params.url && config.params.url === FTRACK_URL) { var ftrackScript = document.createElement('script'); ftrackScript.setAttribute('src', config.params.url); diff --git a/modules/ftrackIdSystem.md b/modules/ftrackIdSystem.md index c5f255c2fc2..0c92f5afab1 100644 --- a/modules/ftrackIdSystem.md +++ b/modules/ftrackIdSystem.md @@ -30,7 +30,12 @@ pbjs.setConfig({ userIds: [{ name: 'FTrack', params: { - url: 'https://d9.flashtalking.com/d9core' // required, if not populated ftrack will not run + url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run + ids: { + 'device id': true, + 'single device id': true, + 'household id': true + } }, storage: { type: 'html5', // "html5" is the required storage type @@ -47,6 +52,12 @@ pbjs.setConfig({ | Param under userSync.userIds[] | Scope | Type | Description | Example | | :-- | :-- | :-- | :-- | :-- | | name | Required | String | The name of this module: `"FTrack"` | `"FTrack"` | +| params | Required | Object | The IDs available, if not populated then the defaults "Device ID" and "Single Device ID" will be returned | | +| params.url | Required | String | The URL for the ftrack library reference. If not populate, ftrack will not run. | 'https://d9.flashtalking.com/d9core' | +| params.ids | Optional | Object | The ftrack IDs available, if not populated then the defaults "Device ID" and "Single Device ID" will be returned | | +| params.ids['device id'] | Optional | Boolean | Should ftrack return "device id". Set to `true` to return it. If set to `undefined` or `false`, ftrack will not return "device id". Default is `false` | `true` | +| params.ids['single device id'] | Optional | Boolean | Should ftrack return "single device id". Set to `true` to return it. If set to `undefined` or `false`, ftrack will not return "single device id". Default is `false` | `true` | +| params.ids['household id'] | Optional; _Requires pairing with either "device id" or "single device id"_ | Boolean | __1.__ Should ftrack return "household id". Set to `true` to attempt to return it. If set to `undefined` or `false`, ftrack will not return "household id". Default is `false`. __2.__ _This will only return "household id" if value of this field is `true` **AND** "household id" is defined on the device._ __3.__ _"household id" requires either "device id" or "single device id" to be also set to `true`, otherwise ftrack will not return "household id"._ | `true` | | storage | Required | Object | Storage settings for how the User ID module will cache the FTrack ID locally | | | storage.type | Required | String | This is where the results of the user ID will be stored. FTrack **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. FTrack **requires** `"FTrackId"`. | `"FTrackId"` | diff --git a/modules/malltvAnalyticsAdapter.js b/modules/malltvAnalyticsAdapter.js index 3431681ef2f..a0e2a208bc9 100644 --- a/modules/malltvAnalyticsAdapter.js +++ b/modules/malltvAnalyticsAdapter.js @@ -184,5 +184,5 @@ malltvAnalyticsAdapter.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: malltvAnalyticsAdapter, - code: 'malltvAnalytics' + code: 'malltv' }) diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js new file mode 100644 index 00000000000..a4af7133370 --- /dev/null +++ b/modules/mobfoxpbBidAdapter.js @@ -0,0 +1,135 @@ +import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'mobfoxpb'; +const AD_URL = 'https://bes.mobfox.com/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.placementId); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const winTop = getWindowTop(); + const location = winTop.location; + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + bidfloor: getBidFloor(bid) + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.traffic = BANNER; + placement.sizes = mediaType[BANNER].sizes; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.traffic = VIDEO; + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.playerSize = mediaType[VIDEO].playerSize; + placement.minduration = mediaType[VIDEO].minduration; + placement.maxduration = mediaType[VIDEO].maxduration; + placement.mimes = mediaType[VIDEO].mimes; + placement.protocols = mediaType[VIDEO].protocols; + placement.startdelay = mediaType[VIDEO].startdelay; + placement.placement = mediaType[VIDEO].placement; + placement.skip = mediaType[VIDEO].skip; + placement.skipafter = mediaType[VIDEO].skipafter; + placement.minbitrate = mediaType[VIDEO].minbitrate; + placement.maxbitrate = mediaType[VIDEO].maxbitrate; + placement.delivery = mediaType[VIDEO].delivery; + placement.playbackmethod = mediaType[VIDEO].playbackmethod; + placement.api = mediaType[VIDEO].api; + placement.linearity = mediaType[VIDEO].linearity; + } else if (mediaType && mediaType[NATIVE]) { + placement.traffic = NATIVE; + placement.native = mediaType[NATIVE]; + } + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + resItem.meta = resItem.meta || {}; + resItem.meta.advertiserDomains = resItem.adomain || []; + + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/mobfoxpbBidAdapter.md b/modules/mobfoxpbBidAdapter.md index 6eb549919d7..f434b2792a9 100644 --- a/modules/mobfoxpbBidAdapter.md +++ b/modules/mobfoxpbBidAdapter.md @@ -24,7 +24,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testBanner' } } ] @@ -41,7 +41,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testVideo' } } ] @@ -63,7 +63,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testNative' } } ] diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 365d692e614..91508d38ca0 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,9 +1,10 @@ -import { isStr, _each, getBidIdParameter } from '../src/utils.js'; +import { isStr, _each, parseUrl, getWindowTop, getBidIdParameter } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; +const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction'; const SYNC_ENDPOINT = 'https://statics.nextmillmedia.com/load-cookie.html?v=4'; const TIME_TO_LIVE = 360; @@ -61,9 +62,12 @@ export const spec = { } } + const urlParameters = parseUrl(getWindowTop().location.href).search; + const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; + requests.push({ method: 'POST', - url: ENDPOINT, + url: isTest ? TEST_ENDPOINT : ENDPOINT, data: JSON.stringify(postBody), options: { contentType: 'application/json', diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 0689d3b04ce..814a2f55299 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,5 +1,6 @@ import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; +import { transformBidderParamKeywords } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -8,8 +9,11 @@ const BIDDER_URL = 'https://fast.nexx360.io/prebid'; const CACHE_URL = 'https://fast.nexx360.io/cache'; const METRICS_TRACKER_URL = 'https://fast.nexx360.io/track-imp'; +const GVLID = 965; + export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['revenuemaker'], // short code supportedMediaTypes: [BANNER, VIDEO], /** @@ -19,6 +23,9 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { + if (!!bid.params.bidfloorCurrency && !['EUR', 'USD'].includes(bid.params.bidfloorCurrency)) return false; + if (!!bid.params.bidfloor && typeof bid.params.bidfloor !== 'number') return false; + if (!!bid.params.keywords && typeof bid.params.keywords !== 'object') return false; return !!(bid.params.account && bid.params.tagId); }, /** @@ -34,7 +41,7 @@ export const spec = { let userEids = null; Object.keys(validBidRequests).forEach(key => { adunitValue = validBidRequests[key]; - adUnits.push({ + const foo = { account: adunitValue.params.account, tagId: adunitValue.params.tagId, videoExt: adunitValue.params.videoExt, @@ -42,8 +49,12 @@ export const spec = { bidId: adunitValue.bidId, auctionId: adunitValue.auctionId, transactionId: adunitValue.transactionId, - mediatypes: adunitValue.mediaTypes - }); + mediatypes: adunitValue.mediaTypes, + bidfloor: adunitValue.params.bidfloor || 0, + bidfloorCurrency: adunitValue.params.bidfloorCurrency || 'USD', + keywords: adunitValue.params.keywords ? transformBidderParamKeywords(adunitValue.params.keywords) : [], + } + adUnits.push(foo); if (adunitValue.userIdAsEids) userEids = adunitValue.userIdAsEids; }); const payload = { @@ -78,7 +89,6 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { const serverBody = serverResponse.body; - // const headerValue = serverResponse.headers.get('some-response-header'); const bidResponses = []; let bidResponse = null; let value = null; diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index a0e91a480a2..e81bdfa9e6a 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -1,10 +1,21 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {logInfo, logError, getBidIdParameter, _each, getValue, isFn, isPlainObject} from '../src/utils.js'; +import { + logInfo, + logError, + getBidIdParameter, + _each, + getValue, + isFn, + isPlainObject, + isArray, + isStr, + isNumber, +} from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'otm'; const OTM_BID_URL = 'https://ssp.otm-r.com/adjson'; -const DEF_CUR = 'RUB' +const DEFAULT_CURRENCY = 'RUB' export const spec = { @@ -19,7 +30,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!bid.params.tid; + return Boolean(bid.params.tid); }, /** @@ -33,43 +44,41 @@ export const spec = { logInfo('validBidRequests', validBidRequests); const bidRequests = []; - let tz = new Date().getTimezoneOffset() - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; - } + const tz = new Date().getTimezoneOffset() + const referrer = bidderRequest && bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : ''; _each(validBidRequests, (bid) => { - let domain = getValue(bid.params, 'domain') || '' - let tid = getValue(bid.params, 'tid') - let cur = getValue(bid.params, 'currency') || DEF_CUR - let bidid = getBidIdParameter('bidId', bid) - let transactionid = getBidIdParameter('transactionId', bid) - let auctionid = getBidIdParameter('auctionId', bid) - let bidfloor = _getBidFloor(bid) + let topOrigin = '' + try { + if (isStr(referrer)) topOrigin = new URL(referrer).host + } catch (e) { /* do nothing */ } + const domain = isStr(bid.params.domain) ? bid.params.domain : topOrigin + const cur = getValue(bid.params, 'currency') || DEFAULT_CURRENCY + const bidid = getBidIdParameter('bidId', bid) + const transactionid = getBidIdParameter('transactionId', bid) + const auctionid = getBidIdParameter('auctionId', bid) + const bidfloor = _getBidFloor(bid) _each(bid.sizes, size => { - let width = 0; - let height = 0; - if (size.length && typeof size[0] === 'number' && typeof size[1] === 'number') { - width = size[0]; - height = size[1]; - } + const hasSizes = isArray(size) && isNumber(size[0]) && isNumber(size[1]) + const width = hasSizes ? size[0] : 0; + const height = hasSizes ? size[1] : 0; + bidRequests.push({ method: 'GET', url: OTM_BID_URL, data: { - tz: tz, + tz, w: width, h: height, - domain: domain, + domain, l: referrer, - s: tid, - cur: cur, - bidid: bidid, - transactionid: transactionid, - auctionid: auctionid, - bidfloor: bidfloor, + s: bid.params.tid, + cur, + bidid, + transactionid, + auctionid, + bidfloor, }, }) }) @@ -81,10 +90,9 @@ export const spec = { * Generate response. * * @param serverResponse - * @param request * @returns {[]|*[]} */ - interpretResponse: function (serverResponse, request) { + interpretResponse: function (serverResponse) { logInfo('serverResponse', serverResponse.body); const responsesBody = serverResponse ? serverResponse.body : {}; @@ -102,7 +110,7 @@ export const spec = { width: bid.w, height: bid.h, creativeId: bid.creativeid, - currency: bid.currency || 'RUB', + currency: bid.currency || DEFAULT_CURRENCY, netRevenue: true, ad: bid.ad, ttl: bid.ttl, @@ -132,12 +140,12 @@ function _getBidFloor(bid) { return bid.params.bidfloor ? bid.params.bidfloor : 0; } - let floor = bid.getFloor({ - currency: DEF_CUR, + const floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, mediaType: '*', size: '*' }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEF_CUR) { + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) { return floor.floor; } return 0; diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index d30fd5bf810..62f550eb216 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -38,6 +38,7 @@ import {find, includes} from '../../src/polyfill.js'; import { S2S_VENDORS } from './config.js'; import { ajax } from '../../src/ajax.js'; import {hook} from '../../src/hook.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; const getConfig = config.getConfig; @@ -755,21 +756,64 @@ Object.assign(ORTB2.prototype, { deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', storedAuctionResponseBid.storedAuctionResponse.toString()); } - const getFloorBid = find(firstBidRequest.bids, bid => bid.adUnitCode === adUnit.code && typeof bid.getFloor === 'function'); + const floor = (() => { + // we have to pick a floor for the imp - here we attempt to find the minimum floor + // across all bids for this adUnit + + const convertCurrency = typeof getGlobal().convertCurrency !== 'function' + ? (amount) => amount + : (amount, from, to) => { + if (from === to) return amount; + let result = null; + try { + result = getGlobal().convertCurrency(amount, from, to); + } catch (e) { + } + return result; + } + const s2sCurrency = config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY; + + return adUnit.bids + .map((bid) => this.getBidRequest(imp.id, bid.bidder)) + .map((bid) => { + if (!bid || typeof bid.getFloor !== 'function') return; + try { + const {currency, floor} = bid.getFloor({ + currency: s2sCurrency + }); + return { + currency, + floor: parseFloat(floor) + } + } catch (e) { + logError('PBS: getFloor threw an error: ', e); + } + }) + .reduce((min, floor) => { + // if any bid does not have a valid floor, do not attempt to send any to PBS + if (floor == null || floor.currency == null || floor.floor == null || isNaN(floor.floor)) { + min.min = null; + } + if (min.min === null) { + return min; + } + // otherwise, pick the minimum one (or, in some strange confluence of circumstances, the one in the best currency) + if (min.ref == null) { + min.ref = min.min = floor; + } else { + const value = convertCurrency(floor.floor, floor.currency, min.ref.currency); + if (value != null && value < min.ref.floor) { + min.ref.floor = value; + min.min = floor; + } + } + return min; + }, {}).min + })(); - if (getFloorBid) { - let floorInfo; - try { - floorInfo = getFloorBid.getFloor({ - currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, - }); - } catch (e) { - logError('PBS: getFloor threw an error: ', e); - } - if (floorInfo && floorInfo.currency && !isNaN(parseFloat(floorInfo.floor))) { - imp.bidfloor = parseFloat(floorInfo.floor); - imp.bidfloorcur = floorInfo.currency - } + if (floor) { + imp.bidfloor = floor.floor; + imp.bidfloorcur = floor.currency } if (imp.banner || imp.video || imp.native) { diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 0667ac0fc74..d64c6092afa 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -977,6 +977,25 @@ function _blockedIabCategoriesValidation(payload, blockedIabCategories) { } } +function _allowedIabCategoriesValidation(payload, allowedIabCategories) { + allowedIabCategories = allowedIabCategories + .filter(function(category) { + if (typeof category === 'string') { // returns only strings + return true; + } else { + logWarn(LOG_WARN_PREFIX + 'acat: Each category should be a string, ignoring category: ' + category); + return false; + } + }) + .map(category => category.trim()) // trim all categories + .filter((category, index, arr) => arr.indexOf(category) === index); // return unique values only + + if (allowedIabCategories.length > 0) { + logWarn(LOG_WARN_PREFIX + 'acat: Selected: ', allowedIabCategories); + payload.ext.acat = allowedIabCategories; + } +} + function _assignRenderer(newBid, request) { let bidParams, context, adUnitCode; if (request.bidderRequest && request.bidderRequest.bids) { @@ -1082,6 +1101,7 @@ export const spec = { var dctrArr = []; var bid; var blockedIabCategories = []; + var allowedIabCategories = []; validBidRequests.forEach(originalBid => { bid = deepClone(originalBid); @@ -1113,6 +1133,9 @@ export const spec = { if (bid.params.hasOwnProperty('bcat') && isArray(bid.params.bcat)) { blockedIabCategories = blockedIabCategories.concat(bid.params.bcat); } + if (bid.params.hasOwnProperty('acat') && isArray(bid.params.acat)) { + allowedIabCategories = allowedIabCategories.concat(bid.params.acat); + } var impObj = _createImpressionObject(bid, conf); if (impObj) { payload.imp.push(impObj); @@ -1182,7 +1205,7 @@ export const spec = { } _handleEids(payload, validBidRequests); - _blockedIabCategoriesValidation(payload, blockedIabCategories); + _handleFlocId(payload, validBidRequests); // First Party Data const commonFpd = config.getConfig('ortb2') || {}; @@ -1192,7 +1215,16 @@ export const spec = { if (commonFpd.user) { mergeDeep(payload, {user: commonFpd.user}); } - + if (commonFpd.bcat) { + blockedIabCategories = blockedIabCategories.concat(commonFpd.bcat) + } + if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) { + const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat; + _allowedIabCategoriesValidation(payload, acatParams); + } else if (allowedIabCategories.length) { + _allowedIabCategoriesValidation(payload, allowedIabCategories); + } + _blockedIabCategoriesValidation(payload, blockedIabCategories); // Note: Do not move this block up // if site object is set in Prebid config then we need to copy required fields from site into app and unset the site object if (typeof config.getConfig('app') === 'object') { diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 05dcf15909a..a071cdfea80 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -8,7 +8,7 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; -const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=nativendo'; +const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; @@ -124,7 +124,8 @@ export const spec = { user: {}, regs: { ext: { - gdpr: 0 + gdpr: 0, + pb_ver: '$prebid.version$' } } }; @@ -187,11 +188,14 @@ registerBidder(spec); function parseNative(bid) { const {assets, link, imptrackers} = bid.adm.native; + let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); + if (link.clicktrackers) { link.clicktrackers.forEach(function (clicktracker, index) { link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); }); } + if (imptrackers) { imptrackers.forEach(function (imptracker, index) { imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -199,8 +203,8 @@ function parseNative(bid) { } const result = { - url: link.url, - clickUrl: link.url, + url: clickUrl, + clickUrl: clickUrl, clickTrackers: link.clicktrackers || undefined, impressionTrackers: imptrackers || undefined }; diff --git a/modules/userId/index.js b/modules/userId/index.js index e656673befb..44fbc871bb5 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -125,11 +125,6 @@ * @property {(function|undefined)} callback - function that will return an id */ -/** - * @typedef {Object} RefreshUserIdsOptions - * @property {(string[]|undefined)} submoduleNames - submodules to refresh - */ - import {find, includes} from '../../src/polyfill.js'; import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; @@ -199,6 +194,8 @@ export let auctionDelay; /** @type {(string|undefined)} */ let ppidSource; +let configListener; + /** @param {Submodule[]} submodules */ export function setSubmoduleRegistry(submodules) { submoduleRegistry = submodules; @@ -473,8 +470,8 @@ function getSubmoduleId(submodules, sourceName) { return {}; } const submodule = submodules.filter(sub => isPlainObject(sub.idObj) && - Object.keys(sub.idObj).length && USER_IDS_CONFIG[Object.keys(sub.idObj)[0]].source === sourceName) - return !isEmpty(submodule) ? submodule[0].idObj : [] + Object.keys(sub.idObj).length && USER_IDS_CONFIG[Object.keys(sub.idObj)[0]]?.source === sourceName); + return !isEmpty(submodule) ? submodule[0].idObj : []; } /** @@ -519,59 +516,133 @@ function addIdDataToAdUnitBids(adUnits, submodules) { }); } -/** - * This is a common function that will initialize subModules if not already done and it will also execute subModule callbacks - */ -function initializeSubmodulesAndExecuteCallbacks(continueAuction) { - let delayed = false; - - // initialize submodules only when undefined - if (typeof initializedSubmodules === 'undefined') { - initializedSubmodules = initSubmodules(submodules, gdprDataHandler.getConsentData()); - if (initializedSubmodules.length) { - setPrebidServerEidPermissions(initializedSubmodules); - // list of submodules that have callbacks that need to be executed - const submodulesWithCallbacks = initializedSubmodules.filter(item => isFn(item.callback)); - - if (submodulesWithCallbacks.length) { - if (continueAuction && auctionDelay > 0) { - // delay auction until ids are available - delayed = true; - let continued = false; - const continueCallback = function () { - if (!continued) { - continued = true; - continueAuction(); - } - } - logInfo(`${MODULE_NAME} - auction delayed by ${auctionDelay} at most to fetch ids`); +function delayFor(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} - timeoutID = setTimeout(continueCallback, auctionDelay); - processSubmoduleCallbacks(submodulesWithCallbacks, continueCallback); +function idSystemInitializer({delay = delayFor} = {}) { + /** + * @returns a {promise, resolve, reject} trio where `promise` is resolved by calling `resolve` or `reject`. + */ + function breakpoint() { + const [SUCCESS, FAIL, RESULT] = [0, 1, 2]; + const status = {}; + + function finisher(slot) { + return function (val) { + if (status[slot] != null) { + status[slot](val); } else { - // wait for auction complete before processing submodule callbacks - events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { - events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); - - // when syncDelay is zero, process callbacks now, otherwise delay process with a setTimeout - if (syncDelay > 0) { - setTimeout(function () { - processSubmoduleCallbacks(submodulesWithCallbacks); - }, syncDelay); - } else { - processSubmoduleCallbacks(submodulesWithCallbacks); - } - }); + status[slot] = true; + status[RESULT] = val; } } } + + return { + promise: new Promise((resolve, reject) => { + if (status[SUCCESS] != null) { + resolve(status[RESULT]); + } else if (status[FAIL] != null) { + reject(status[RESULT]); + } else { + status[SUCCESS] = resolve; + status[FAIL] = reject; + } + }), + resolve: finisher(SUCCESS), + reject: finisher(FAIL) + } } - if (continueAuction && !delayed) { - continueAuction(); + const startInit = breakpoint(); + const startCallbacks = breakpoint(); + let cancel; + let initialized = false; + + function cancelAndTry(promise) { + if (cancel != null) { + cancel.reject(); + } + cancel = breakpoint(); + return Promise.race([promise, cancel.promise]); } + + // grab a reference to global vars so that the promise chains remain isolated; + // multiple calls to `init` (from tests) might otherwise cause them to interfere with each other + let initModules = initializedSubmodules; + let allModules = submodules; + + function checkRefs(fn) { + // unfortunately tests have their own global state that needs to be guarded, so even if we keep ours tidy, + // we cannot let things like submodule callbacks run (they pollute things like the global `server` XHR mock) + return function(...args) { + if (initModules === initializedSubmodules && allModules === submodules) { + return fn(...args); + } + } + } + + let done = cancelAndTry( + startInit.promise + .then(() => gdprDataHandler.promise) + .then(checkRefs((consentData) => { + initSubmodules(initModules, allModules, consentData); + })) + .then(() => startCallbacks.promise) + .then(checkRefs(() => { + const modWithCb = initModules.filter(item => isFn(item.callback)); + if (modWithCb.length) { + return new Promise((resolve) => processSubmoduleCallbacks(modWithCb, resolve)); + } + })) + ); + + /** + * with `ready` = true, starts initialization; with `refresh` = true, reinitialize submodules (optionally + * filtered by `submoduleNames`). + */ + return function ({refresh = false, submoduleNames = null, ready = false} = {}) { + if (ready && !initialized) { + initialized = true; + startInit.resolve(); + // submodule callbacks should run immediately if `auctionDelay` > 0, or `syncDelay` ms after the + // auction ends otherwise + if (auctionDelay > 0) { + startCallbacks.resolve(); + } else { + events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { + events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); + delay(syncDelay).then(startCallbacks.resolve); + }); + } + } + if (refresh) { + done = cancelAndTry( + done + .catch(() => null) + .then(() => gdprDataHandler.promise) // fetch again in case a refresh was forced before this was resolved + .then(checkRefs((consentData) => { + const cbModules = initSubmodules( + initModules, + allModules.filter((sm) => submoduleNames == null || submoduleNames.includes(sm.submodule.name)), + consentData, + true + ).filter((sm) => { + return sm.callback != null; + }); + if (cbModules.length) { + return new Promise((resolve) => processSubmoduleCallbacks(cbModules, resolve)); + } + })) + ); + } + return done; + }; } +let initIdSystem; + /** * Hook is executed before adapters, but after consentManagement. Consent data is requied because * this module requires GDPR consent with Purpose #1 to save data locally. @@ -581,9 +652,11 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export function requestBidsHook(fn, reqBidsConfigObj) { - // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(function () { +export function requestBidsHook(fn, reqBidsConfigObj, {delay = delayFor} = {}) { + Promise.race([ + getUserIdsAsync(), + delay(auctionDelay) + ]).then(() => { // pass available user id data to bid adapters addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); @@ -616,9 +689,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) { * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIds() { - // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(); - return getCombinedSubmoduleIds(initializedSubmodules); + return getCombinedSubmoduleIds(initializedSubmodules) } /** @@ -626,9 +697,7 @@ function getUserIds() { * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIdsAsEids() { - // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(); - return createEidsArray(getCombinedSubmoduleIds(initializedSubmodules)); + return createEidsArray(getUserIds()) } /** @@ -637,33 +706,33 @@ function getUserIdsAsEids() { */ function getUserIdsAsEidBySource(sourceName) { - initializeSubmodulesAndExecuteCallbacks(); return createEidsArray(getSubmoduleId(initializedSubmodules, sourceName))[0]; -}; +} /** * This function will be exposed in global-name-space so that userIds for a source can be exposed * Sample use case is exposing this function to ESP */ function getEncryptedEidsForSource(source, encrypt, customFunction) { - let eidsSignals = {}; - - if (isFn(customFunction)) { - logInfo(`${MODULE_NAME} - Getting encrypted signal from custom function : ${customFunction.name} & source : ${source} `); - // Publishers are expected to define a common function which will be proxy for signal function. - const customSignals = customFunction(source); - eidsSignals[source] = customSignals ? encryptSignals(customSignals) : null; // by default encrypt using base64 to avoid JSON errors - } else { - // initialize signal with eids by default - const eid = getUserIdsAsEidBySource(source); - logInfo(`${MODULE_NAME} - Getting encrypted signal for eids :${JSON.stringify(eid)}`); - if (!isEmpty(eid)) { - eidsSignals[eid.source] = encrypt === true ? encryptSignals(eid) : eid.uids[0].id; // If encryption is enabled append version (1||) and encrypt entire object + return initIdSystem().then(() => { + let eidsSignals = {}; + + if (isFn(customFunction)) { + logInfo(`${MODULE_NAME} - Getting encrypted signal from custom function : ${customFunction.name} & source : ${source} `); + // Publishers are expected to define a common function which will be proxy for signal function. + const customSignals = customFunction(source); + eidsSignals[source] = customSignals ? encryptSignals(customSignals) : null; // by default encrypt using base64 to avoid JSON errors + } else { + // initialize signal with eids by default + const eid = getUserIdsAsEidBySource(source); + logInfo(`${MODULE_NAME} - Getting encrypted signal for eids :${JSON.stringify(eid)}`); + if (!isEmpty(eid)) { + eidsSignals[eid.source] = encrypt === true ? encryptSignals(eid) : eid.uids[0].id; // If encryption is enabled append version (1||) and encrypt entire object + } } - } - const promise = Promise.resolve(eidsSignals[source]); - logInfo(`${MODULE_NAME} - Fetching encrypted eids: ${eidsSignals[source]}`); - return promise; + logInfo(`${MODULE_NAME} - Fetching encrypted eids: ${eidsSignals[source]}`); + return eidsSignals[source]; + }) } function encryptSignals(signals, version = 1) { @@ -705,57 +774,37 @@ function registerSignalSources() { } /** -* This function will be exposed in the global-name-space so that userIds can be refreshed after initialization. -* @param {RefreshUserIdsOptions} options -*/ -function refreshUserIds(options, callback) { - let submoduleNames = options ? options.submoduleNames : null; - if (!submoduleNames) { - submoduleNames = []; - } - - initializeSubmodulesAndExecuteCallbacks(function() { - let consentData = gdprDataHandler.getConsentData() - - // gdpr consent with purpose one is required, otherwise exit immediately - let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); - if (!hasValidated && !hasGDPRConsent(consentData)) { - logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); - return; - } - - // we always want the latest consentData stored, even if we don't execute any submodules - const storedConsentData = getStoredConsentData(); - setStoredConsentData(consentData); - - let callbackSubmodules = []; - for (let submodule of userIdModules) { - if (submoduleNames.length > 0 && - submoduleNames.indexOf(submodule.submodule.name) === -1) { - continue; - } - - logInfo(`${MODULE_NAME} - refreshing ${submodule.submodule.name}`); - populateSubmoduleId(submodule, consentData, storedConsentData, true); - updateInitializedSubmodules(submodule); - - if (initializedSubmodules.length) { - setPrebidServerEidPermissions(initializedSubmodules); - } - - if (isFn(submodule.callback)) { - callbackSubmodules.push(submodule); + * Force (re)initialization of ID submodules. + * + * This will force a refresh of the specified ID submodules regardless of `auctionDelay` / `syncDelay` settings, and + * return a promise that resolves to the same value as `getUserIds()` when the refresh is complete. + * If a refresh is already in progress, it will be canceled (rejecting promises returned by previous calls to `refreshUserIds`). + * + * @param submoduleNames? submodules to refresh. If omitted, refresh all submodules. + * @param callback? called when the refresh is complete + */ +function refreshUserIds({submoduleNames} = {}, callback) { + return initIdSystem({refresh: true, submoduleNames}) + .then(() => { + if (callback && isFn(callback)) { + callback(); } - } - - if (callbackSubmodules.length > 0) { - processSubmoduleCallbacks(callbackSubmodules); - } + return getUserIds(); + }); +} - if (callback) { - callback(); - } - }); +/** + * @returns a promise that resolves to the same value as `getUserIds()`, but only once all ID submodules have completed + * initialization. This can also be used to synchronize calls to other ID accessors, e.g. + * + * ``` + * pbjs.getUserIdsAsync().then(() => { + * const eids = pbjs.getUserIdsAsEids(); // guaranteed to be completely initialized at this point + * }); + * ``` + */ +function getUserIdsAsync() { + return initIdSystem().then(() => getUserIds(), () => getUserIdsAsync()); } /** @@ -816,12 +865,7 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef } } -/** - * @param {SubmoduleContainer[]} submodules - * @param {ConsentData} consentData - * @returns {SubmoduleContainer[]} initialized submodules - */ -function initSubmodules(submodules, consentData) { +function initSubmodules(dest, submodules, consentData, forceRefresh = false) { // gdpr consent with purpose one is required, otherwise exit immediately let { userIdModules, hasValidated } = validateGdprEnforcement(submodules, consentData); if (!hasValidated && !hasGDPRConsent(consentData)) { @@ -833,25 +877,30 @@ function initSubmodules(submodules, consentData) { const storedConsentData = getStoredConsentData(); setStoredConsentData(consentData); - return userIdModules.reduce((carry, submodule) => { - populateSubmoduleId(submodule, consentData, storedConsentData, false); + const initialized = userIdModules.reduce((carry, submodule) => { + populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh); carry.push(submodule); return carry; }, []); + if (initialized.length) { + setPrebidServerEidPermissions(initialized); + } + initialized.forEach(updateInitializedSubmodules.bind(null, dest)); + return initialized; } -function updateInitializedSubmodules(submodule) { +function updateInitializedSubmodules(dest, submodule) { let updated = false; - for (let i = 0; i < initializedSubmodules.length; i++) { - if (submodule.config.name.toLowerCase() === initializedSubmodules[i].config.name.toLowerCase()) { + for (let i = 0; i < dest.length; i++) { + if (submodule.config.name.toLowerCase() === dest[i].config.name.toLowerCase()) { updated = true; - initializedSubmodules[i] = submodule; + dest[i] = submodule; break; } } if (!updated) { - initializedSubmodules.push(submodule); + dest.push(submodule); } } @@ -900,8 +949,9 @@ function updateSubmodules() { // do this to avoid reprocessing submodules const addedSubmodules = submoduleRegistry.filter(i => !find(submodules, j => j.name === i.name)); + submodules.splice(0, submodules.length); // find submodule and the matching configuration, if found create and append a SubmoduleContainer - submodules = addedSubmodules.map(i => { + addedSubmodules.map(i => { const submoduleConfig = find(configs, j => j.name && (j.name.toLowerCase() === i.name.toLowerCase() || (i.aliasName && j.name.toLowerCase() === i.aliasName.toLowerCase()))); if (submoduleConfig && i.name !== submoduleConfig.name) submoduleConfig.name = i.name; @@ -912,7 +962,8 @@ function updateSubmodules() { callback: undefined, idObj: undefined } : null; - }).filter(submodule => submodule !== null); + }).filter(submodule => submodule !== null) + .forEach((sm) => submodules.push(sm)); if (!addedUserIdHook && submodules.length) { // priority value 40 will load after consentManagement with a priority of 50 @@ -938,12 +989,17 @@ export function attachIdSystem(submodule) { * so a callback is added to fire after the consentManagement module. * @param {{getConfig:function}} config */ -export function init(config) { +export function init(config, {delay = delayFor} = {}) { ppidSource = undefined; submodules = []; configRegistry = []; addedUserIdHook = false; - initializedSubmodules = undefined; + initializedSubmodules = []; + initIdSystem = idSystemInitializer({delay}); + if (configListener != null) { + configListener(); + } + submoduleRegistry = []; // list of browser enabled storage types validStorageTypes = [ @@ -962,7 +1018,7 @@ export function init(config) { } // listen for config userSyncs to be set - config.getConfig('userSync', conf => { + configListener = config.getConfig('userSync', conf => { // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 const userSync = conf.userSync; ppidSource = userSync.ppid; @@ -971,6 +1027,7 @@ export function init(config) { syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : NO_AUCTION_DELAY; updateSubmodules(); + initIdSystem({ready: true}); } }); @@ -980,6 +1037,7 @@ export function init(config) { (getGlobal()).getEncryptedEidsForSource = getEncryptedEidsForSource; (getGlobal()).registerSignalSources = registerSignalSources; (getGlobal()).refreshUserIds = refreshUserIds; + (getGlobal()).getUserIdsAsync = getUserIdsAsync; (getGlobal()).getUserIdsAsEidBySource = getUserIdsAsEidBySource; } diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js index b6fe51c43bc..0613f722af8 100644 --- a/modules/vibrantmediaBidAdapter.js +++ b/modules/vibrantmediaBidAdapter.js @@ -6,13 +6,14 @@ * Note: Only BANNER and VIDEO are currently supported by the prebid server. */ -import {logError, logInfo} from '../src/utils.js'; +import {logError, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {OUTSTREAM} from '../src/video.js'; const BIDDER_CODE = 'vibrantmedia'; const VIBRANT_MEDIA_PREBID_URL = 'https://prebid.intellitxt.com/prebid'; +const VALID_PIXEL_URL_REGEX = /^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+([/?].*)?$/; const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; /** @@ -51,6 +52,10 @@ const isBaseUrl = function(url) { return (endOfDomain === -1) || (endOfDomain === (urlMinusScheme.length - 1)); }; +const isValidPixelUrl = function (candidateUrl) { + return VALID_PIXEL_URL_REGEX.test(candidateUrl); +}; + /** * Returns transformed bid requests that are in a format native to the prebid server. * @@ -213,7 +218,9 @@ export const spec = { * @param {*} bidData the data associated with the won bid. See example above for data format. */ onBidWon: function(bidData) { - logInfo('Bid won: ' + JSON.stringify(bidData)); + if (bidData && bidData.meta && isValidPixelUrl(bidData.meta.wp)) { + triggerPixel(`${bidData.meta.wp}${bidData.status}`); + } } }; diff --git a/package-lock.json b/package-lock.json index e40addfc54d..c4e86036f99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.20.0-pre", + "version": "6.23.0-pre", "lockfileVersion": 2, "requires": true, "packages": { @@ -31,11 +31,12 @@ "@wdio/concise-reporter": "^7.5.2", "@wdio/local-runner": "^7.5.2", "@wdio/mocha-framework": "^7.5.2", - "@wdio/spec-reporter": "^7.5.2", + "@wdio/spec-reporter": "^7.19.0", "@wdio/sync": "^7.5.2", "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", + "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", @@ -3277,14 +3278,14 @@ } }, "node_modules/@wdio/spec-reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.16.14.tgz", - "integrity": "sha512-hpS2rLXo91lfrit5/pjDSbff2lqQe+k07/JPOJ48W+ZPSI+ib7rSldI4JFYU4YuKN1TnhkbhxRBMib3bF3Fs+Q==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.1.tgz", + "integrity": "sha512-qnZkn3VcyBPtcorUtpyCFE8v5ubyWmR7mFETXNzyriHyvjvk+NeFCWaFcIehpXYXiAmNpAwyfnZoIY6tkKQixQ==", "dev": true, "dependencies": { "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.16.14", - "@wdio/types": "7.16.14", + "@wdio/reporter": "7.19.1", + "@wdio/types": "7.19.1", "chalk": "^4.0.0", "easy-table": "^1.1.1", "pretty-ms": "^7.0.0" @@ -3296,6 +3297,58 @@ "@wdio/cli": "^7.0.0" } }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.1.tgz", + "integrity": "sha512-sWmBBV4dPCZkGk9Qq0m35T/vHGen0N10nH4osQcVP3IZJqpo2eLIH4w+X6EUbjZ2GdgOA2bLMMzb1bl9JqnGPg==", + "dev": true, + "dependencies": { + "@types/diff": "^5.0.0", + "@types/node": "^17.0.4", + "@types/object-inspect": "^1.8.0", + "@types/supports-color": "^8.1.0", + "@types/tmp": "^0.2.0", + "@wdio/types": "7.19.1", + "diff": "^5.0.0", + "fs-extra": "^10.0.0", + "object-inspect": "^1.10.3", + "supports-color": "8.1.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/types": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.1.tgz", + "integrity": "sha512-mOodKlmvYxpj8P5BhjggEGpXuiRSlsyn2ClG8QqJ3lfXgOtOVEzFNfv/Ai7TkHr+lHDQNXLjllCjSqoCHhwlqg==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "^4.6.2" + } + }, "node_modules/@wdio/spec-reporter/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4291,6 +4344,113 @@ "node": ">=0.8.0" } }, + "node_modules/babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "node_modules/babel-core/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-core/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/babel-core/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/babel-core/node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "dependencies": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "node_modules/babel-generator/node_modules/detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-generator/node_modules/jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, "node_modules/babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -4336,6 +4496,15 @@ "node": ">=4.0.0" } }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -4392,6 +4561,50 @@ "babel-runtime": "^6.22.0" } }, + "node_modules/babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "dependencies": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "node_modules/babel-register/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/babel-register/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/babel-register/node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -4408,6 +4621,81 @@ "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", "hasInstallScript": true }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-traverse/node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-traverse/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babel-types/node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/babelify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", @@ -4420,6 +4708,15 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -5337,14 +5634,20 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001314", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz", - "integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==", + "version": "1.0.30001320", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", + "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/caseless": { "version": "0.12.0", @@ -12765,6 +13068,19 @@ "node": "*" } }, + "node_modules/home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -13094,6 +13410,15 @@ "node": ">= 0.10" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -15579,6 +15904,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -16637,9 +16974,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minimist-options": { @@ -17735,6 +18072,15 @@ "readable-stream": "^2.0.1" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -18331,6 +18677,15 @@ "node": ">=0.8" } }, + "node_modules/private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -21501,6 +21856,15 @@ "node": ">=8" } }, + "node_modules/trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/trough": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", @@ -21616,6 +21980,20 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/typescript-compare": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", @@ -25688,19 +26066,58 @@ } }, "@wdio/spec-reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.16.14.tgz", - "integrity": "sha512-hpS2rLXo91lfrit5/pjDSbff2lqQe+k07/JPOJ48W+ZPSI+ib7rSldI4JFYU4YuKN1TnhkbhxRBMib3bF3Fs+Q==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.1.tgz", + "integrity": "sha512-qnZkn3VcyBPtcorUtpyCFE8v5ubyWmR7mFETXNzyriHyvjvk+NeFCWaFcIehpXYXiAmNpAwyfnZoIY6tkKQixQ==", "dev": true, "requires": { "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.16.14", - "@wdio/types": "7.16.14", + "@wdio/reporter": "7.19.1", + "@wdio/types": "7.19.1", "chalk": "^4.0.0", "easy-table": "^1.1.1", "pretty-ms": "^7.0.0" }, "dependencies": { + "@wdio/reporter": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.1.tgz", + "integrity": "sha512-sWmBBV4dPCZkGk9Qq0m35T/vHGen0N10nH4osQcVP3IZJqpo2eLIH4w+X6EUbjZ2GdgOA2bLMMzb1bl9JqnGPg==", + "dev": true, + "requires": { + "@types/diff": "^5.0.0", + "@types/node": "^17.0.4", + "@types/object-inspect": "^1.8.0", + "@types/supports-color": "^8.1.0", + "@types/tmp": "^0.2.0", + "@wdio/types": "7.19.1", + "diff": "^5.0.0", + "fs-extra": "^10.0.0", + "object-inspect": "^1.10.3", + "supports-color": "8.1.1" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/types": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.1.tgz", + "integrity": "sha512-mOodKlmvYxpj8P5BhjggEGpXuiRSlsyn2ClG8QqJ3lfXgOtOVEzFNfv/Ai7TkHr+lHDQNXLjllCjSqoCHhwlqg==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -26496,6 +26913,105 @@ } } }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + } + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, "babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -26530,6 +27046,15 @@ } } }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, "babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -26577,6 +27102,47 @@ "babel-runtime": "^6.22.0" } }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + } + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -26593,6 +27159,79 @@ } } }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } + } + }, "babelify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", @@ -26600,6 +27239,12 @@ "dev": true, "requires": {} }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -27323,9 +27968,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001314", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz", - "integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==", + "version": "1.0.30001320", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", + "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==", "dev": true }, "caseless": { @@ -33269,6 +33914,16 @@ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -33517,6 +34172,15 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -35473,6 +36137,15 @@ "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -36279,9 +36952,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minimist-options": { @@ -37161,6 +37834,12 @@ "readable-stream": "^2.0.1" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -37601,6 +38280,12 @@ "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", "dev": true }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -40163,6 +40848,12 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, "trough": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", @@ -40255,6 +40946,13 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "peer": true + }, "typescript-compare": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", diff --git a/package.json b/package.json index 47bcc94404f..9e5d68ac5e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.20.0-pre", + "version": "6.23.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -43,11 +43,12 @@ "@wdio/concise-reporter": "^7.5.2", "@wdio/local-runner": "^7.5.2", "@wdio/mocha-framework": "^7.5.2", - "@wdio/spec-reporter": "^7.5.2", + "@wdio/spec-reporter": "^7.19.0", "@wdio/sync": "^7.5.2", "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", + "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 87bc7a45491..382c93b0d25 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -14,6 +14,7 @@ import { ADPOD } from '../mediaTypes.js'; import { getHook, hook } from '../hook.js'; import { getCoreStorageManager } from '../storageManager.js'; import {auctionManager} from '../auctionManager.js'; +import { bidderSettings } from '../bidderSettings.js'; export const storage = getCoreStorageManager('bidderFactory'); @@ -235,6 +236,11 @@ export function newBidder(spec) { onBid: (bid) => { const bidRequest = bidRequestMap[bid.requestId]; if (bidRequest) { + bid.adapterCode = bidRequest.bidder; + if (isInvalidAlternateBidder(bid.bidderCode, bidRequest.bidder)) { + logWarn(`${bid.bidderCode} is not a registered partner or known bidder of ${bidRequest.bidder}, hence continuing without bid. If you wish to support this bidder, please mark allowAlternateBidderCodes as true in bidderSettings.`); + return; + } // creating a copy of original values as cpm and currency are modified later bid.originalCpm = bid.cpm; bid.originalCurrency = bid.currency; @@ -250,6 +256,17 @@ export function newBidder(spec) { } }); + function isInvalidAlternateBidder(responseBidder, requestBidder) { + let allowAlternateBidderCodes = bidderSettings.get(requestBidder, 'allowAlternateBidderCodes'); + let alternateBiddersList = bidderSettings.get(requestBidder, 'allowedAlternateBidderCodes'); + if (!!responseBidder && !!requestBidder && requestBidder !== responseBidder) { + if ((allowAlternateBidderCodes !== undefined && !allowAlternateBidderCodes) || (isArray(alternateBiddersList) && (alternateBiddersList[0] !== '*' && !alternateBiddersList.includes(responseBidder)))) { + return true; + } + } + return false; + } + function registerSyncs(responses, gdprConsent, uspConsent) { registerSyncInner(spec, responses, gdprConsent, uspConsent); } diff --git a/src/auction.js b/src/auction.js index ae5c3c6156b..c55334bcd3d 100644 --- a/src/auction.js +++ b/src/auction.js @@ -848,13 +848,7 @@ function groupByPlacement(bidsByPlacement, bid) { function getTimedOutBids(bidderRequests, timelyBidders) { const timedOutBids = bidderRequests .map(bid => (bid.bids || []).filter(bid => !timelyBidders.has(bid.bidder))) - .reduce(flatten, []) - .map(bid => ({ - bidId: bid.bidId, - bidder: bid.bidder, - adUnitCode: bid.adUnitCode, - auctionId: bid.auctionId, - })); + .reduce(flatten, []); return timedOutBids; } diff --git a/test/fake-server/bundle.js b/test/fake-server/bundle.js new file mode 100644 index 00000000000..b0430458083 --- /dev/null +++ b/test/fake-server/bundle.js @@ -0,0 +1,41 @@ +const fs = require('fs'); +const path = require('path'); +const makeBundle = require('../../gulpfile.js'); +const argv = require('yargs').argv; +const host = argv.host || 'localhost'; +const port = argv.port || 4444; +const dev = argv.dev || false; + +const REPLACE = { + 'https://ib.adnxs.com/ut/v3/prebid': `http://${host}:${port}/appnexus` +}; + +const replaceStrings = (() => { + const rules = Object.entries(REPLACE).map(([orig, repl]) => { + return [new RegExp(orig, 'g'), repl]; + }); + return function(text) { + return rules.reduce((text, [pat, repl]) => text.replace(pat, repl), text); + } +})(); + +const getBundle = (() => { + const cache = {}; + return function (modules = []) { + modules = Array.isArray(modules) ? [...modules] : [modules]; + modules.sort(); + const key = modules.join(','); + if (!cache.hasOwnProperty(key)) { + cache[key] = makeBundle(modules, dev).then(replaceStrings); + } + return cache[key]; + } +})(); + +module.exports = function (req, res, next) { + res.type('text/javascript'); + getBundle(req.query.modules).then((bundle) => { + res.write(bundle); + next(); + }).catch(next); +} diff --git a/test/fake-server/fake-responder.js b/test/fake-server/fake-responder.js index c884b00ca9c..a44d02260e7 100644 --- a/test/fake-server/fake-responder.js +++ b/test/fake-server/fake-responder.js @@ -6,9 +6,6 @@ const path = require('path'); // path to the fixture directory const fixturesPath = path.join(__dirname, 'fixtures'); -// An object storing 'Request-Response' pairs. -let REQ_RES_MAP = generateFixtures(fixturesPath); - /** * Matches 'req.body' with the responseBody pair * @param {object} requestBody - `req.body` of incoming request hitting middleware 'fakeResponder'. @@ -16,8 +13,8 @@ let REQ_RES_MAP = generateFixtures(fixturesPath); */ const matchResponse = function (requestBody) { let actualUuids = []; - - const requestResponsePairs = Object.keys(REQ_RES_MAP).map(testName => REQ_RES_MAP[testName]); + let reqResMap = generateFixtures(fixturesPath); + const requestResponsePairs = Object.keys(reqResMap).map(testName => reqResMap[testName]); // delete 'uuid' property requestBody.tags.forEach(body => { @@ -38,8 +35,22 @@ const matchResponse = function (requestBody) { requestResponsePairs .forEach(reqRes => { reqRes.request.httpRequest && reqRes.request.httpRequest.body.tags.forEach(body => body.uuid && delete body.uuid) }); + const match = requestResponsePairs.filter(reqRes => reqRes.request.httpRequest && deepEqual(reqRes.request.httpRequest.body.tags, requestBody.tags)); + + try { + if (match.length === 0) { + throw new Error('No mock response found'); + } else if (match.length > 1) { + throw new Error('More than one mock response found') + } + } catch (e) { + console.error(e); + console.error('Tags:', JSON.stringify(requestBody.tags, null, 2)); + throw e; + } + // match the 'actual' requestBody with the 'expected' requestBody and find the 'responseBody' - const responseBody = requestResponsePairs.filter(reqRes => reqRes.request.httpRequest && deepEqual(reqRes.request.httpRequest.body.tags, requestBody.tags))[0].response.httpResponse.body; + const responseBody = match[0].response.httpResponse.body; // ENABLE THE FOLLOWING CODE FOR TROUBLE-SHOOTING FAKED REQUESTS; COMMENT AGAIN WHEN DONE // console.log('value found for responseBody:', responseBody); diff --git a/test/fake-server/fixtures/basic-banner/request.json b/test/fake-server/fixtures/basic-banner/request.json index ea85b5a6842..6b355cd24c0 100644 --- a/test/fake-server/fixtures/basic-banner/request.json +++ b/test/fake-server/fixtures/basic-banner/request.json @@ -58,4 +58,4 @@ "user": {} } } -} \ No newline at end of file +} diff --git a/test/fake-server/fixtures/basic-outstream/request.json b/test/fake-server/fixtures/basic-outstream/request.json index 611a518fc2d..e9f3302ab4c 100644 --- a/test/fake-server/fixtures/basic-outstream/request.json +++ b/test/fake-server/fixtures/basic-outstream/request.json @@ -20,7 +20,7 @@ "disable_psa": true, "video": { "skippable": true, - "playback_method": ["auto_play_sound_off"] + "playback_method": 2 }, "hb_source": 1 }, { @@ -40,11 +40,11 @@ "disable_psa": true, "video": { "skippable": true, - "playback_method": ["auto_play_sound_off"] + "playback_method": 2 }, "hb_source": 1 }], "user": {} } } -} \ No newline at end of file +} diff --git a/test/fake-server/index.js b/test/fake-server/index.js index 752648c6746..e93bcfd465f 100644 --- a/test/fake-server/index.js +++ b/test/fake-server/index.js @@ -5,8 +5,9 @@ const morgan = require('morgan'); const bodyParser = require('body-parser'); const argv = require('yargs').argv; const fakeResponder = require('./fake-responder.js'); +const bundleMaker = require('./bundle.js'); -const PORT = argv.port || '3000'; +const PORT = argv.port || '4444'; // Initialize express app const app = express(); @@ -24,7 +25,11 @@ app.use(function(req, res, next) { next(); }); -app.post('/', fakeResponder, (req, res) => { +app.get('/bundle', bundleMaker, (req, res) => { + res.send(); +}); + +app.post('/appnexus', fakeResponder, (req, res) => { res.send(); }); diff --git a/test/helpers/consentData.js b/test/helpers/consentData.js new file mode 100644 index 00000000000..17ddc583f88 --- /dev/null +++ b/test/helpers/consentData.js @@ -0,0 +1,6 @@ +import {gdprDataHandler} from 'src/adapterManager.js'; + +export function mockGdprConsent(sandbox, getConsentData = () => null) { + sandbox.stub(gdprDataHandler, 'promise').get(() => Promise.resolve(getConsentData())); + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(getConsentData) +} diff --git a/test/helpers/testing-utils.js b/test/helpers/testing-utils.js index 76e2b652a79..81f22ca471d 100644 --- a/test/helpers/testing-utils.js +++ b/test/helpers/testing-utils.js @@ -1,13 +1,51 @@ /* eslint-disable no-console */ -module.exports = { +const {expect} = require('chai'); +const utils = { host: (process.env.TEST_SERVER_HOST) ? process.env.TEST_SERVER_HOST : 'localhost', protocol: (process.env.TEST_SERVER_PROTOCOL) ? 'https' : 'http', + testPageURL: function(name) { + return `${utils.protocol}://${utils.host}:9999/test/pages/${name}` + }, waitForElement: function(elementRef, time = 2000) { let element = $(elementRef); element.waitForExist({timeout: time}); }, - switchFrame: function(frameRef, frameName) { + switchFrame: function(frameRef) { let iframe = $(frameRef); browser.switchToFrame(iframe); + }, + loadAndWaitForElement(url, selector, pause = 3000, timeout = 2000, retries = 3, attempt = 1) { + browser.url(url); + browser.pause(pause); + if (selector != null) { + try { + utils.waitForElement(selector, timeout); + } catch (e) { + if (attempt < retries) { + utils.loadAndWaitForElement(url, selector, pause, timeout, retries, attempt + 1); + } + } + } + }, + setupTest({url, waitFor, expectGAMCreative = null, pause = 3000, timeout = 2000, retries = 3}, name, fn) { + describe(name, function () { + this.retries(retries); + before(() => utils.loadAndWaitForElement(url, waitFor, pause, timeout, retries)); + fn.call(this); + if (expectGAMCreative) { + expectGAMCreative = expectGAMCreative === true ? waitFor : expectGAMCreative; + it(`should render GAM creative`, () => { + utils.switchFrame(expectGAMCreative); + const creative = [ + '> a > img', // banner + '> div[class="card"]' // native + ].map((child) => `body > div[class="GoogleActiveViewElement"] ${child}`) + .join(', '); + expect($(creative).isExisting()).to.be.true; + }); + } + }); } } + +module.exports = utils; diff --git a/test/pages/banner.html b/test/pages/banner.html index 75993cefb39..2e88d356647 100644 --- a/test/pages/banner.html +++ b/test/pages/banner.html @@ -7,7 +7,7 @@ Prebid.js Banner Example - + diff --git a/test/pages/bidderSettings.html b/test/pages/bidderSettings.html index 015ad3ca45f..205fc250be1 100644 --- a/test/pages/bidderSettings.html +++ b/test/pages/bidderSettings.html @@ -1,7 +1,7 @@ - + + + - + - +