diff --git a/PR_REVIEW.md b/PR_REVIEW.md index dac50593d6e..6402fcbbbaa 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -41,6 +41,7 @@ For modules and core platform updates, the initial reviewer should request an ad - Requests to the bidder should support HTTPS - Responses from the bidder should be compressed (such as gzip, compress, deflate) - Bid responses may not use JSONP: All requests must be AJAX with JSON responses +- Video openrtb params should be read from the ad unit when available; however bidder config can override the ad unit. - All user-sync (aka pixel) activity must be registered via the provided functions - Adapters may not use the $$PREBID_GLOBAL$$ variable - All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables. diff --git a/README.md b/README.md index 0dd5b25a50f..b3f33b27aa5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ This README is for developers who want to contribute to Prebid.js. Additional documentation can be found at [the Prebid homepage](http://prebid.org). Working examples can be found in [the developer docs](http://prebid.org/dev-docs/getting-started.html). +Prebid.js is open source software that is offered for free as a convenience. While it is designed to help companies address legal requirements associated with header bidding, we cannot and do not warrant that your use of Prebid.js will satisfy legal requirements. You are solely responsible for ensuring that your use of Prebid.js complies with all applicable laws. We strongly encourage you to obtain legal advice when using Prebid.js to ensure your implementation complies with all laws where you operate. + **Table of Contents** - [Usage](#Usage) diff --git a/allowedModules.js b/allowedModules.js index 2a521f781f9..d8e8b69f593 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -21,7 +21,6 @@ module.exports = { 'fun-hooks/no-eval', 'just-clone', 'dlv', - 'dset', - 'deep-equal' + 'dset' ] }; diff --git a/integrationExamples/gpt/digitrust_Full.html b/integrationExamples/gpt/digitrust_Full.html deleted file mode 100644 index fc7704776f4..00000000000 --- a/integrationExamples/gpt/digitrust_Full.html +++ /dev/null @@ -1,222 +0,0 @@ - - - Full DigiTrust Prebid Sample - - - - - - - - - - - - - -

DigiTrust Prebid Full Sample

- - -

- This sample shows the simplest integration path for using DigiTrust ID with Prebid. - You can use DigiTrust ID without integrating the entire DigiTrust suite. -

- -
- -
- -
- - - - diff --git a/integrationExamples/gpt/digitrust_Simple.html b/integrationExamples/gpt/digitrust_Simple.html deleted file mode 100644 index 2581c6ce7cc..00000000000 --- a/integrationExamples/gpt/digitrust_Simple.html +++ /dev/null @@ -1,230 +0,0 @@ - - - Simple DigiTrust Prebid - No Framework - - - - - - - - - - - - - - -

DigiTrust Prebid Sample - No Framework

- -

- This sample shows the simplest integration path for using DigiTrust ID with Prebid. - You can use DigiTrust ID without integrating the entire DigiTrust suite. -

-
- -
- -
- - diff --git a/integrationExamples/gpt/digitrust_cmp_test.html b/integrationExamples/gpt/digitrust_cmp_test.html deleted file mode 100644 index 6f0a70188f3..00000000000 --- a/integrationExamples/gpt/digitrust_cmp_test.html +++ /dev/null @@ -1,192 +0,0 @@ - - - CMP Simple DigiTrust Prebid - No Framework - - - - - - - - - - - - - -

DigiTrust Prebid Sample - No Framework

- -

- This sample tests cmp behavior with simple integration path for using DigiTrust ID with Prebid. - You can use DigiTrust ID without integrating the entire DigiTrust suite. -

-
- -
- -
- - - diff --git a/modules/.submodules.json b/modules/.submodules.json index ba8af4e6550..58ab8798fa5 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -2,7 +2,6 @@ "userId": [ "unifiedIdSystem", "pubCommonIdSystem", - "digiTrustIdSystem", "id5IdSystem", "parrableIdSystem", "britepoolIdSystem", diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 5df2af7f32e..798b6450946 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -167,7 +167,7 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl // Allow the ability to configure the HB endpoint for testing purposes. const ttxSettings = config.getConfig('ttxSettings'); - const url = (ttxSettings && ttxSettings.url) || END_POINT; + const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`; // Return the server request return { diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index b2ef5dafb41..b4c2a6ac0d6 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -5,16 +5,18 @@ import { loadExternalScript } from '../src/adloader.js' import JSEncrypt from 'jsencrypt/bin/jsencrypt.js'; import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; - -const BIDDER_CODE = 'adagio'; -const VERSION = '2.2.2'; -const FEATURES_VERSION = '1'; -const ENDPOINT = 'https://mp.4dex.io/prebid'; -const SUPPORTED_MEDIA_TYPES = ['banner']; -const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; -const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; -const GVLID = 617; -const storage = getStorageManager(GVLID, 'adagio'); +import { getRefererInfo } from '../src/refererDetection.js'; + +export const BIDDER_CODE = 'adagio'; +export const LOG_PREFIX = 'Adagio:'; +export const VERSION = '2.3.0'; +export const FEATURES_VERSION = '1'; +export const ENDPOINT = 'https://mp.4dex.io/prebid'; +export const SUPPORTED_MEDIA_TYPES = ['banner']; +export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; +export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; +export const GVLID = 617; +export const storage = getStorageManager(GVLID, 'adagio'); export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 @@ -23,17 +25,19 @@ t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj pV6EP3MTLosuUEpLaQIDAQAB -----END PUBLIC KEY-----`; +let currentWindow; + export function adagioScriptFromLocalStorageCb(ls) { try { if (!ls) { - utils.logWarn('Adagio Script not found'); + utils.logWarn(`${LOG_PREFIX} script not found.`); return; } const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/; if (!hashRgx.test(ls)) { - utils.logWarn('No hash found in Adagio script'); + utils.logWarn(`${LOG_PREFIX} no hash found.`); storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); } else { const r = ls.match(hashRgx); @@ -44,21 +48,32 @@ export function adagioScriptFromLocalStorageCb(ls) { jsEncrypt.setPublicKey(ADAGIO_PUBKEY); if (jsEncrypt.verify(content, hash, sha256)) { - utils.logInfo('Start Adagio script'); + utils.logInfo(`${LOG_PREFIX} start script.`); Function(ls)(); // eslint-disable-line no-new-func } else { - utils.logWarn('Invalid Adagio script found'); + utils.logWarn(`${LOG_PREFIX} invalid script found.`); storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); } } } catch (err) { - // + utils.logError(LOG_PREFIX, err); } } export function getAdagioScript() { storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => { - adagioScriptFromLocalStorageCb(ls) + internal.adagioScriptFromLocalStorageCb(ls) + }); + + storage.localStorageIsEnabled(isValid => { + if (isValid) { + loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE); + } else { + // ensure adagio removing for next time. + // It's an antipattern regarding the TCF2 enforcement logic + // but it's the only way to respect the user choice update. + window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + } }); } @@ -72,113 +87,197 @@ function canAccessTopWindow() { } } +function getCurrentWindow() { + return currentWindow || utils.getWindowSelf(); +} + +function isSafeFrameWindow() { + const ws = utils.getWindowSelf(); + return !!(ws.$sf && ws.$sf.ext); +} + function initAdagio() { - const w = utils.getWindowTop(); + if (canAccessTopWindow()) { + currentWindow = (canAccessTopWindow()) ? utils.getWindowTop() : utils.getWindowSelf(); + } + + const w = internal.getCurrentWindow(); w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; w.ADAGIO.queue = w.ADAGIO.queue || []; w.ADAGIO.versions = w.ADAGIO.versions || {}; w.ADAGIO.versions.adagioBidderAdapter = VERSION; + w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); getAdagioScript(); - - loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE) -} - -if (canAccessTopWindow()) { - initAdagio(); } export const _features = { - getPrintNumber: function (adUnitCode) { - const adagioAdUnit = _getOrAddAdagioAdUnit(adUnitCode); + getPrintNumber(adUnitCode) { + const adagioAdUnit = internal.getOrAddAdagioAdUnit(adUnitCode); return adagioAdUnit.printNumber || 1; }, - getPageDimensions: function () { - const viewportDims = _features.getViewPortDimensions().split('x'); - const w = utils.getWindowTop(); - const body = w.document.body; + getPageDimensions() { + if (isSafeFrameWindow() || !canAccessTopWindow()) { + return ''; + } + + // the page dimension can be computed on window.top only. + const wt = utils.getWindowTop(); + const body = wt.document.querySelector('body'); + if (!body) { - return '' + return ''; } - const html = w.document.documentElement; + const html = wt.document.documentElement; + const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); - return viewportDims[0] + 'x' + pageHeight; + return `${pageWidth}x${pageHeight}`; }, - getViewPortDimensions: function () { - let viewPortWidth; - let viewPortHeight; - const w = utils.getWindowTop(); - const d = w.document; + getViewPortDimensions() { + if (!isSafeFrameWindow() && !canAccessTopWindow()) { + return ''; + } + + const viewportDims = { w: 0, h: 0 }; - if (w.innerWidth) { - viewPortWidth = w.innerWidth; - viewPortHeight = w.innerHeight; + if (isSafeFrameWindow()) { + const ws = utils.getWindowSelf(); + + if (typeof ws.$sf.ext.geom !== 'function') { + utils.logWarn(`${LOG_PREFIX} cannot use the $sf.ext.geom() safeFrame API method`); + return ''; + } + + const sfGeom = ws.$sf.ext.geom().win; + viewportDims.w = Math.round(sfGeom.w); + viewportDims.h = Math.round(sfGeom.h); } else { - viewPortWidth = d.getElementsByTagName('body')[0].clientWidth; - viewPortHeight = d.getElementsByTagName('body')[0].clientHeight; + // window.top based computing + const wt = utils.getWindowTop(); + + if (wt.innerWidth) { + viewportDims.w = wt.innerWidth; + viewportDims.h = wt.innerHeight; + } else { + const d = wt.document; + const body = d.querySelector('body'); + + if (!body) { + return ''; + } + + viewportDims.w = d.querySelector('body').clientWidth; + viewportDims.h = d.querySelector('body').clientHeight; + } } - return viewPortWidth + 'x' + viewPortHeight; + return `${viewportDims.w}x${viewportDims.h}`; }, - isDomLoading: function () { - const w = utils.getWindowTop(); - let performance = w.performance || w.msPerformance || w.webkitPerformance || w.mozPerformance; - let domLoading = -1; + /** + * domLoading feature is computed on window.top if reachable. + */ + getDomLoadingDuration() { + let domLoadingDuration = -1; + let performance; + + performance = (canAccessTopWindow()) ? utils.getWindowTop().performance : utils.getWindowSelf().performance; if (performance && performance.timing && performance.timing.navigationStart > 0) { const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) domLoading = val; + if (val > 0) { + domLoadingDuration = val; + } } - return domLoading; + + return domLoadingDuration; }, - getSlotPosition: function (element) { - if (!element) return ''; - - const w = utils.getWindowTop(); - const d = w.document; - const el = element; - - let box = el.getBoundingClientRect(); - const docEl = d.documentElement; - const body = d.body; - const clientTop = d.clientTop || body.clientTop || 0; - const clientLeft = d.clientLeft || body.clientLeft || 0; - const scrollTop = w.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = w.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - const elComputedStyle = w.getComputedStyle(el, null); - const elComputedDisplay = elComputedStyle.display || 'block'; - const mustDisplayElement = elComputedDisplay === 'none'; - - if (mustDisplayElement) { - el.style = el.style || {}; - el.style.display = 'block'; - box = el.getBoundingClientRect(); - el.style.display = elComputedDisplay; + getSlotPosition(params) { + const { adUnitElementId, postBid } = params; + + if (!adUnitElementId) { + return ''; } - const position = { - x: Math.round(box.left + scrollLeft - clientLeft), - y: Math.round(box.top + scrollTop - clientTop) - }; + if (!isSafeFrameWindow() && !canAccessTopWindow()) { + return ''; + } + + const position = { x: 0, y: 0 }; + + if (isSafeFrameWindow()) { + const ws = utils.getWindowSelf(); + + if (typeof ws.$sf.ext.geom !== 'function') { + utils.logWarn(`${LOG_PREFIX} cannot use the $sf.ext.geom() safeFrame API method`); + return ''; + } - return position.x + 'x' + position.y; + const sfGeom = ws.$sf.ext.geom().self; + position.x = Math.round(sfGeom.t); + position.y = Math.round(sfGeom.l); + } else if (canAccessTopWindow()) { + // window.top based computing + const wt = utils.getWindowTop(); + const d = wt.document; + + let domElement; + + if (postBid === true) { + const ws = utils.getWindowSelf(); + const currentElement = ws.document.getElementById(adUnitElementId); + domElement = internal.getElementFromTopWindow(currentElement, ws); + } else { + domElement = wt.document.getElementById(adUnitElementId); + } + + if (!domElement) { + return ''; + } + + let box = domElement.getBoundingClientRect(); + + const docEl = d.documentElement; + const body = d.body; + const clientTop = d.clientTop || body.clientTop || 0; + const clientLeft = d.clientLeft || body.clientLeft || 0; + const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const elComputedStyle = wt.getComputedStyle(domElement, null); + const elComputedDisplay = elComputedStyle.display || 'block'; + const mustDisplayElement = elComputedDisplay === 'none'; + + if (mustDisplayElement) { + domElement.style = domElement.style || {}; + domElement.style.display = 'block'; + box = domElement.getBoundingClientRect(); + domElement.style.display = elComputedDisplay; + } + position.x = Math.round(box.left + scrollLeft - clientLeft); + position.y = Math.round(box.top + scrollTop - clientTop); + } else { + return ''; + } + + return `${position.x}x${position.y}`; }, - getTimestamp: function () { + getTimestampUTC() { + // timestamp returned in seconds return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; }, - getDevice: function () { - if (!canAccessTopWindow()) return false; - const w = utils.getWindowTop(); - const ua = w.navigator.userAgent; + getDevice() { + const ws = utils.getWindowSelf(); + const ua = ws.navigator.userAgent; if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { return 5; // "tablet" @@ -189,52 +288,83 @@ export const _features = { return 2; // personal computers }, - getBrowser: function () { - const w = utils.getWindowTop(); - const ua = w.navigator.userAgent; + getBrowser() { + const ws = utils.getWindowSelf(); + const ua = ws.navigator.userAgent; const uaLowerCase = ua.toLowerCase(); - return /Edge\/\d./i.test(ua) ? 'edge' : uaLowerCase.indexOf('chrome') > 0 ? 'chrome' : uaLowerCase.indexOf('firefox') > 0 ? 'firefox' : uaLowerCase.indexOf('safari') > 0 ? 'safari' : uaLowerCase.indexOf('opera') > 0 ? 'opera' : uaLowerCase.indexOf('msie') > 0 || w.MSStream ? 'ie' : 'unknow'; + return /Edge\/\d./i.test(ua) ? 'edge' : uaLowerCase.indexOf('chrome') > 0 ? 'chrome' : uaLowerCase.indexOf('firefox') > 0 ? 'firefox' : uaLowerCase.indexOf('safari') > 0 ? 'safari' : uaLowerCase.indexOf('opera') > 0 ? 'opera' : uaLowerCase.indexOf('msie') > 0 || ws.MSStream ? 'ie' : 'unknow'; }, - getOS: function () { - const w = window.top; - const ua = w.navigator.userAgent; + getOS() { + const ws = utils.getWindowSelf(); + const ua = ws.navigator.userAgent; const uaLowerCase = ua.toLowerCase(); return uaLowerCase.indexOf('linux') > 0 ? 'linux' : uaLowerCase.indexOf('mac') > 0 ? 'mac' : uaLowerCase.indexOf('win') > 0 ? 'windows' : ''; + }, + + getUrl(refererInfo) { + // top has not been reached, it means we are not sure + // to get the proper page url. + if (!refererInfo.reachedTop) { + return; + } + return refererInfo.referer; + }, + + getUrlFromParams(params) { + const { postBidOptions } = params; + if (postBidOptions && postBidOptions.url) { + return postBidOptions.url; + } } -} +}; -function _pushInAdagioQueue(ob) { - try { - if (!canAccessTopWindow()) return; - const w = utils.getWindowTop(); - w.ADAGIO.queue.push(ob); - } catch (e) {} +function enqueue(ob) { + const w = internal.getCurrentWindow(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.queue.push(ob); }; -function _getOrAddAdagioAdUnit(adUnitCode) { - const w = utils.getWindowTop(); +function getOrAddAdagioAdUnit(adUnitCode) { + const w = internal.getCurrentWindow(); + + w.ADAGIO = w.ADAGIO || {}; + if (w.ADAGIO.adUnits[adUnitCode]) { return w.ADAGIO.adUnits[adUnitCode] } + return w.ADAGIO.adUnits[adUnitCode] = {}; -} +}; + +function getPageviewId() { + const w = internal.getCurrentWindow(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID(); + + return w.ADAGIO.pageviewId; +}; -function _computePrintNumber(adUnitCode) { +function computePrintNumber(adUnitCode) { let printNumber = 1; - const w = utils.getWindowTop(); + const w = internal.getCurrentWindow(); + if ( w.ADAGIO && w.ADAGIO.adUnits && w.ADAGIO.adUnits[adUnitCode] && - w.ADAGIO.adUnits[adUnitCode].pageviewId === _getPageviewId() && + w.ADAGIO.adUnits[adUnitCode].pageviewId === internal.getPageviewId() && w.ADAGIO.adUnits[adUnitCode].printNumber ) { printNumber = parseInt(w.ADAGIO.adUnits[adUnitCode].printNumber, 10) + 1; } + return printNumber; -} +}; -function _getDevice() { +function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { userAgent: navigator.userAgent, @@ -246,35 +376,63 @@ function _getDevice() { }; }; -function _getSite() { - const w = utils.getWindowTop(); +function getSite(bidderRequest) { + let domain = ''; + let page = ''; + let referrer = ''; + + const { refererInfo } = bidderRequest; + + if (canAccessTopWindow()) { + const wt = utils.getWindowTop(); + domain = wt.location.hostname; + page = wt.location.href; + referrer = wt.document.referrer || ''; + } else if (refererInfo.reachedTop) { + const url = utils.parseUrl(refererInfo.referer); + domain = url.hostname; + page = refererInfo.referer; + } else if (refererInfo.stack && refererInfo.stack.length && refererInfo.stack[0]) { + // important note check if refererInfo.stack[0] is 'thruly' because a `null` value + // will be considered as "localhost" by the parseUrl function. + // As the isBidRequestValid returns false when it does not reach the referer + // this should never called. + const url = utils.parseUrl(refererInfo.stack[0]); + domain = url.hostname; + } + return { - domain: w.location.hostname, - page: w.location.href, - referrer: w.document.referrer || '' + domain, + page, + referrer }; }; -function _getPageviewId() { - if (!canAccessTopWindow()) return false; - const w = utils.getWindowTop(); - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID(); - return w.ADAGIO.pageviewId; -}; +function getElementFromTopWindow(element, currentWindow) { + try { + if (utils.getWindowTop() === currentWindow) { + if (!element.getAttribute('id')) { + element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`); + } + return element; + } else { + const frame = currentWindow.frameElement; + const frameClientRect = frame.getBoundingClientRect(); + const elementClientRect = element.getBoundingClientRect(); + + if (frameClientRect.width !== elementClientRect.width || frameClientRect.height !== elementClientRect.height) { + return false; + } -function _getElementFromTopWindow(element, currentWindow) { - if (utils.getWindowTop() === currentWindow) { - if (!element.getAttribute('id')) { - element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`); + return getElementFromTopWindow(frame, currentWindow.parent); } - return element; - } else { - const frame = currentWindow.frameElement; - return _getElementFromTopWindow(frame, currentWindow.parent); + } catch (err) { + utils.logWarn(`${LOG_PREFIX}`, err); + return false; } -} +}; -export function _autoDetectAdUnitElementId(adUnitCode) { +function autoDetectAdUnitElementId(adUnitCode) { const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode) let adUnitElementId = null; @@ -283,9 +441,9 @@ export function _autoDetectAdUnitElementId(adUnitCode) { } return adUnitElementId; -} +}; -function _autoDetectEnvironment() { +function autoDetectEnvironment() { const device = _features.getDevice(); let environment; switch (device) { @@ -300,53 +458,37 @@ function _autoDetectEnvironment() { break; }; return environment -} +}; -/** - * Returns all features for a specific adUnit element - * - * @param {Object} bidRequest - * @returns {Object} features for an element (see specs) - */ -function _getFeatures(bidRequest) { - if (!canAccessTopWindow()) return; - const w = utils.getWindowTop(); - const adUnitCode = bidRequest.adUnitCode; - const adUnitElementId = bidRequest.params.adUnitElementId || _autoDetectAdUnitElementId(adUnitCode); - let element; +function getFeatures(bidRequest, bidderRequest) { + const { adUnitCode, params } = bidRequest; + const { adUnitElementId } = params; + const { refererInfo } = bidderRequest; if (!adUnitElementId) { - utils.logWarn('Unable to detect adUnitElementId. Adagio measures won\'t start'); - } else { - if (bidRequest.params.postBid === true) { - window.document.getElementById(adUnitElementId); - element = _getElementFromTopWindow(element, window); - w.ADAGIO.pbjsAdUnits.map((adUnit) => { - if (adUnit.code === adUnitCode) { - const outerElementId = element.getAttribute('id'); - adUnit.outerAdUnitElementId = outerElementId; - bidRequest.params.outerAdUnitElementId = outerElementId; - } - }); - } else { - element = w.document.getElementById(adUnitElementId); - } + utils.logWarn(`${LOG_PREFIX} unable to get params.adUnitElementId. Continue without tiv.`); } const features = { - print_number: _features.getPrintNumber(bidRequest.adUnitCode).toString(), + print_number: _features.getPrintNumber(adUnitCode).toString(), page_dimensions: _features.getPageDimensions().toString(), viewport_dimensions: _features.getViewPortDimensions().toString(), - dom_loading: _features.isDomLoading().toString(), + dom_loading: _features.getDomLoadingDuration().toString(), // layout: features.getLayout().toString(), - adunit_position: _features.getSlotPosition(element).toString(), - user_timestamp: _features.getTimestamp().toString(), + adunit_position: _features.getSlotPosition(params).toString(), + user_timestamp: _features.getTimestampUTC().toString(), device: _features.getDevice().toString(), - url: w.location.origin + w.location.pathname, + url: _features.getUrl(refererInfo) || _features.getUrlFromParams(params) || '', browser: _features.getBrowser(), os: _features.getOS() }; + Object.keys(features).forEach((prop) => { + if (features[prop] === '') { + delete features[prop]; + } + }); + const adUnitFeature = {}; adUnitFeature[adUnitElementId] = { @@ -354,7 +496,7 @@ function _getFeatures(bidRequest) { version: FEATURES_VERSION }; - _pushInAdagioQueue({ + internal.enqueue({ action: 'features', ts: Date.now(), data: adUnitFeature @@ -363,22 +505,53 @@ function _getFeatures(bidRequest) { return features; }; +export const internal = { + enqueue, + getOrAddAdagioAdUnit, + getPageviewId, + computePrintNumber, + getDevice, + getSite, + getElementFromTopWindow, + autoDetectAdUnitElementId, + autoDetectEnvironment, + getFeatures, + getRefererInfo, + adagioScriptFromLocalStorageCb, + getCurrentWindow, + canAccessTopWindow +}; + function _getGdprConsent(bidderRequest) { + if (!utils.deepAccess(bidderRequest, 'gdprConsent')) { + return false; + } + + const { + apiVersion, + gdprApplies, + consentString, + allowAuctionWithoutConsent + } = bidderRequest.gdprConsent; + const consent = {}; - if (utils.deepAccess(bidderRequest, 'gdprConsent')) { - if (bidderRequest.gdprConsent.consentString !== undefined) { - consent.consentString = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - consent.consentRequired = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - if (bidderRequest.gdprConsent.allowAuctionWithoutConsent !== undefined) { - consent.allowAuctionWithoutConsent = bidderRequest.gdprConsent.allowAuctionWithoutConsent ? 1 : 0; - } - if (bidderRequest.gdprConsent.apiVersion !== undefined) { - consent.apiVersion = bidderRequest.gdprConsent.apiVersion; - } + + if (apiVersion !== undefined) { + consent.apiVersion = apiVersion + } + + if (consentString !== undefined) { + consent.consentString = consentString; + } + + if (gdprApplies !== undefined) { + consent.consentRequired = (gdprApplies) ? 1 : 0; } + + if (allowAuctionWithoutConsent !== undefined) { + consent.allowAuctionWithoutConsent = allowAuctionWithoutConsent ? 1 : 0; + } + return consent; } @@ -391,84 +564,103 @@ function _getSchain(bidRequest) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaType: SUPPORTED_MEDIA_TYPES, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - isBidRequestValid: function (bid) { + isBidRequestValid(bid) { const { adUnitCode, auctionId, sizes, bidder, params, mediaTypes } = bid; - const { organizationId, site, placement } = bid.params; - const adUnitElementId = bid.params.adUnitElementId || _autoDetectAdUnitElementId(adUnitCode); - const environment = bid.params.environment || _autoDetectEnvironment(); - let isValid = false; + if (!params) { + utils.logWarn(`${LOG_PREFIX} the "params" property is missing.`); + return false; + } - utils.logInfo('adUnitElementId', adUnitElementId) + const { organizationId, site, placement } = params; + const adUnitElementId = params.adUnitElementId || internal.autoDetectAdUnitElementId(adUnitCode); + const environment = params.environment || internal.autoDetectEnvironment(); - try { - if (canAccessTopWindow()) { - const w = utils.getWindowTop(); - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; - isValid = !!(organizationId && site && placement); - - const tempAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode); - - bid.params = { - ...bid.params, - adUnitElementId, - environment - } + // insure auto-detected params are kept in `bid` object. + bid.params = { + ...params, + adUnitElementId, + environment + } - tempAdUnits.push({ - code: adUnitCode, - sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes, - bids: [{ - bidder, - params - }] - }); - w.ADAGIO.pbjsAdUnits = tempAdUnits; - - if (isValid === true) { - let printNumber = _computePrintNumber(adUnitCode); - w.ADAGIO.adUnits[adUnitCode] = { - auctionId: auctionId, - pageviewId: _getPageviewId(), - printNumber - }; - } + const debugData = () => ({ + action: 'pb-dbg', + ts: Date.now(), + data: { + bid } - } catch (e) { - return isValid; + }); + + const refererInfo = internal.getRefererInfo(); + + if (!refererInfo.reachedTop) { + utils.logWarn(`${LOG_PREFIX} the main page url is unreachabled.`); + internal.enqueue(debugData()); + + return false; + } else if (!(organizationId && site && placement)) { + utils.logWarn(`${LOG_PREFIX} at least one required param is missing.`); + internal.enqueue(debugData()); + + return false; } - return isValid; - }, - buildRequests: function (validBidRequests, bidderRequest) { - // AdagioBidAdapter works when window.top can be reached only - if (!bidderRequest.refererInfo.reachedTop) return []; + const w = internal.getCurrentWindow(); + const pageviewId = internal.getPageviewId(); + const printNumber = internal.computePrintNumber(adUnitCode); + + // Store adUnits config. + // If an adUnitCode has already been stored, it will be replaced. + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode) + w.ADAGIO.pbjsAdUnits.push({ + code: adUnitCode, + mediaTypes: mediaTypes || {}, + sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes, + bids: [{ + bidder, + params: bid.params // use the updated bid.params object with auto-detected params + }], + auctionId, + pageviewId, + printNumber + }); + + // (legacy) Store internal adUnit information + w.ADAGIO.adUnits[adUnitCode] = { + auctionId, + pageviewId, + printNumber, + }; + + return true; + }, + buildRequests(validBidRequests, bidderRequest) { const secure = (location.protocol === 'https:') ? 1 : 0; - const device = _getDevice(); - const site = _getSite(); - const pageviewId = _getPageviewId(); - const gdprConsent = _getGdprConsent(bidderRequest); + const device = internal.getDevice(); + const site = internal.getSite(bidderRequest); + const pageviewId = internal.getPageviewId(); + const gdprConsent = _getGdprConsent(bidderRequest) || {}; const schain = _getSchain(validBidRequests[0]); const adUnits = utils._map(validBidRequests, (bidRequest) => { - bidRequest.features = _getFeatures(bidRequest); + bidRequest.features = internal.getFeatures(bidRequest, bidderRequest); return bidRequest; }); - // Regroug ad units by siteId + // Group ad units by organizationId const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { - if (adUnit.params && adUnit.params.organizationId) { - adUnit.params.organizationId = adUnit.params.organizationId.toString(); - } - (groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []).push(adUnit); + adUnit.params.organizationId = adUnit.params.organizationId.toString(); + + groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || [] + groupedAdUnits[adUnit.params.organizationId].push(adUnit); + return groupedAdUnits; }, {}); - // Build one request per siteId - const requests = utils._map(Object.keys(groupedAdUnits), (organizationId) => { + // Build one request per organizationId + const requests = utils._map(Object.keys(groupedAdUnits), organizationId => { return { method: 'POST', url: ENDPOINT, @@ -495,13 +687,13 @@ export const spec = { return requests; }, - interpretResponse: function (serverResponse, bidRequest) { + interpretResponse(serverResponse, bidRequest) { let bidResponses = []; try { const response = serverResponse.body; if (response) { if (response.data) { - _pushInAdagioQueue({ + internal.enqueue({ action: 'ssp-data', ts: Date.now(), data: response.data @@ -528,18 +720,20 @@ export const spec = { return bidResponses; }, - getUserSyncs: function (syncOptions, serverResponses) { + getUserSyncs(syncOptions, serverResponses) { if (!serverResponses.length || serverResponses[0].body === '' || !serverResponses[0].body.userSyncs) { return false; } - const syncs = serverResponses[0].body.userSyncs.map((sync) => { - return { - type: sync.t === 'p' ? 'image' : 'iframe', - url: sync.u - } - }) + + const syncs = serverResponses[0].body.userSyncs.map(sync => ({ + type: sync.t === 'p' ? 'image' : 'iframe', + url: sync.u + })); + return syncs; - } -} + }, +}; + +initAdagio(); registerBidder(spec); diff --git a/modules/adbutlerBidAdapter.js b/modules/adbutlerBidAdapter.js index 47162aa2445..10edd8ae3e3 100644 --- a/modules/adbutlerBidAdapter.js +++ b/modules/adbutlerBidAdapter.js @@ -9,7 +9,7 @@ const BIDDER_CODE = 'adbutler'; export const spec = { code: BIDDER_CODE, pageID: Math.floor(Math.random() * 10e6), - aliases: ['divreach'], + aliases: ['divreach', 'doceree'], isBidRequestValid: function (bid) { return !!(bid.params.accountID && bid.params.zoneID); @@ -25,6 +25,7 @@ export const spec = { let requestURI; let serverRequests = []; let zoneCounters = {}; + let extraParams = {}; for (i = 0; i < validBidRequests.length; i++) { bidRequest = validBidRequests[i]; @@ -32,6 +33,7 @@ export const spec = { accountID = utils.getBidIdParameter('accountID', bidRequest.params); keyword = utils.getBidIdParameter('keyword', bidRequest.params); domain = utils.getBidIdParameter('domain', bidRequest.params); + extraParams = utils.getBidIdParameter('extra', bidRequest.params); if (!(zoneID in zoneCounters)) { zoneCounters[zoneID] = 0; @@ -52,6 +54,13 @@ export const spec = { requestURI += 'kw=' + encodeURIComponent(keyword) + ';'; } + for (let key in extraParams) { + if (extraParams.hasOwnProperty(key)) { + let val = encodeURIComponent(extraParams[key]); + requestURI += `${key}=${val};`; + } + } + zoneCounters[zoneID]++; serverRequests.push({ method: 'GET', diff --git a/modules/adbutlerBidAdapter.md b/modules/adbutlerBidAdapter.md index 5905074270a..1921cc4046e 100644 --- a/modules/adbutlerBidAdapter.md +++ b/modules/adbutlerBidAdapter.md @@ -23,9 +23,12 @@ Module that connects to an AdButler zone to fetch bids. keyword: 'red', //optional minCPM: '1.00', //optional maxCPM: '5.00' //optional + extra: { // optional + foo: "bar" + } } } ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 3c129d1bee1..cd5a44f34d8 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -20,21 +20,28 @@ export const spec = { } const { gdprConsent, refererInfo } = bidderRequest; - const account = getAccount(validBidRequests); const targets = validBidRequests.map(bid => bid.params.data).reduce(mergeTargets, {}); - const gdprParams = (gdprConsent && gdprConsent.consentString) ? [`xt${gdprConsent.consentString}`] : []; - const refererParams = (refererInfo && refererInfo.referer) ? [`xf${base64urlEncode(refererInfo.referer)}`] : []; - const id5Params = (getId5Id(validBidRequests)) ? [`x5${getId5Id(validBidRequests)}`] : []; - const targetsParams = Object.keys(targets).map(targetCode => targetCode + targets[targetCode].join(';')); - const slotsParams = validBidRequests.map(bid => 'sl' + bidToSlotName(bid)); - const params = [...slotsParams, ...targetsParams, ...gdprParams, ...refererParams, ...id5Params].map(s => `/${s}`).join(''); - const cacheBuster = '?t=' + new Date().getTime(); - const uri = 'https://ads-' + account + '.adhese.com/json' + params + cacheBuster; + const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; + const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; + const id5Params = (getId5Id(validBidRequests)) ? { x5: [getId5Id(validBidRequests)] } : {}; + const slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid) })); + + const payload = { + slots: slots, + parameters: { ...targets, ...gdprParams, ...refererParams, ...id5Params } + } + + const account = getAccount(validBidRequests); + const uri = 'https://ads-' + account + '.adhese.com/json'; return { - method: 'GET', + method: 'POST', url: uri, - bids: validBidRequests + data: JSON.stringify(payload), + bids: validBidRequests, + options: { + contentType: 'application/json' + } }; }, @@ -148,7 +155,7 @@ function getbaseAdResponse(response) { } function isAdheseAd(ad) { - return !ad.origin || ad.origin === 'JERLICIA' || ad.origin === 'DALE'; + return !ad.origin || ad.origin === 'JERLICIA'; } function getMediaType(markup) { diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 483d6de52b9..0a0317e1f59 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -48,11 +48,12 @@ function canonicalizeSizesArray(sizes) { return sizes; } -function buildRequestParams(tags, auctionId, transactionId, gdprConsent, uspConsent, refInfo) { +function buildRequestParams(tags, bidderRequest) { + let {auctionId, gdprConsent, uspConsent, transactionId, refererInfo} = bidderRequest; let req = { id: auctionId, tid: transactionId, - site: buildSite(refInfo), + site: buildSite(refererInfo), imp: tags }; if (gdprConsent) { @@ -132,14 +133,13 @@ export const spec = { return acc; }, {}); - let {auctionId, gdprConsent, uspConsent, transactionId, refererInfo} = bidderRequest; let requests = []; Object.keys(dispatch).forEach(host => { Object.keys(dispatch[host]).forEach(pubId => { - let request = buildRequestParams(dispatch[host][pubId], auctionId, transactionId, gdprConsent, uspConsent, refererInfo); + let request = buildRequestParams(dispatch[host][pubId], bidderRequest); requests.push({ method: 'POST', - url: `https://${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled(refererInfo) ? '&debug=1' : ''}`, + url: `https://${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled(bidderRequest.refererInfo) ? '&debug=1' : ''}`, data: JSON.stringify(request) }) }); @@ -159,14 +159,24 @@ export const spec = { }, getUserSyncs: function(syncOptions, serverResponses) { - if (!syncOptions.iframeEnabled || !serverResponses || serverResponses.length === 0) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + if (syncOptions.iframeEnabled) { + return buildSyncs(serverResponses, 'syncpages', 'iframe'); + } else if (syncOptions.pixelEnabled) { + return buildSyncs(serverResponses, 'syncpixels', 'image'); + } else { return []; } - return serverResponses.filter(rps => rps.body && rps.body.syncpages) - .map(rsp => rsp.body.syncpages) - .reduce((a, b) => a.concat(b), []) - .map(syncUrl => ({type: 'iframe', url: syncUrl})); } }; +function buildSyncs(serverResponses, propName, type) { + return serverResponses.filter(rps => rps.body && rps.body[propName]) + .map(rsp => rsp.body[propName]) + .reduce((a, b) => a.concat(b), []) + .map(syncUrl => ({type: type, url: syncUrl})); +} + registerBidder(spec); diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index d069af7d56e..972dd696bf6 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -78,10 +78,11 @@ export const spec = { */ buildRequests: function (bidRequests, bidderRequest) { let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo); - const requests = []; + let requests = []; + let schain = bidRequests[0].schain; Object.keys(impDispatch).forEach(host => { Object.keys(impDispatch[host]).forEach(zoneId => { - const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest); + const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest, schain); requests.push({ method: 'POST', url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, @@ -119,6 +120,9 @@ export const spec = { ttl: 360, netRevenue: true }; + if (rtbBid.dealid !== undefined) { + prBid.dealId = rtbBid.dealid; + } if ('banner' in imp) { prBid.mediaType = BANNER; prBid.width = rtbBid.w; @@ -293,9 +297,10 @@ function getAllowedSyncMethod(bidderCode) { * Builds complete rtb request * @param imps {Object} Collection of rtb impressions * @param bidderRequest {BidderRequest} + * @param schain {Object=} Supply chain config * @return {Object} Complete rtb request */ -function buildRtbRequest(imps, bidderRequest) { +function buildRtbRequest(imps, bidderRequest, schain) { let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest; let req = { @@ -305,6 +310,7 @@ function buildRtbRequest(imps, bidderRequest) { 'at': 1, 'device': { 'ip': 'caller', + 'ipv6': 'caller', 'ua': 'caller', 'js': 1, 'language': getLanguage() @@ -329,6 +335,9 @@ function buildRtbRequest(imps, bidderRequest) { if (syncMethod) { utils.deepSetValue(req, 'ext.adk_usersync', syncMethod); } + if (schain) { + utils.deepSetValue(req, 'source.ext.schain', schain); + } return req; } diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index 306ab76f512..50303b82979 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -63,6 +63,7 @@ export const spec = { for (let i = 0; i < len; i++) { let bid = validBidRequests[i]; let sizes + let identeties = {} if (bid.mediaTypes) { if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { sizes = bid.mediaTypes[BANNER].sizes @@ -70,6 +71,9 @@ export const spec = { sizes = bid.mediaTypes[VIDEO].playerSize } } + if (bid.userId && bid.userId.idl_env) { + identeties.identityLink = bid.userId.idl_env + } placements.push({ placementId: bid.params.placementId, @@ -79,7 +83,8 @@ export const spec = { hPlayer: sizes ? sizes[1] : 0, traffic: bid.params.traffic || BANNER, schain: bid.schain || {}, - keywords: bid.params.keywords || [] + keywords: bid.params.keywords || [], + identeties }); } return { diff --git a/modules/appnexusAnalyticsAdapter.js b/modules/appnexusAnalyticsAdapter.js index d697d31cdd3..868b317d7d4 100644 --- a/modules/appnexusAnalyticsAdapter.js +++ b/modules/appnexusAnalyticsAdapter.js @@ -13,7 +13,8 @@ var appnexusAdapter = adapter({ adapterManager.registerAnalyticsAdapter({ adapter: appnexusAdapter, - code: 'appnexus' + code: 'appnexus', + gvlid: 32 }); export default appnexusAdapter; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index df0b60cb7d7..12bc6a8105c 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -63,7 +63,20 @@ const storage = getStorageManager(GVLID, BIDDER_CODE); export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['appnexusAst', 'brealtime', 'emxdigital', 'pagescience', 'defymedia', 'gourmetads', 'matomy', 'featureforward', 'oftmedia', 'districtm', 'adasta', 'beintoo'], + aliases: [ + { code: 'appnexusAst', gvlid: 32 }, + { code: 'brealtime' }, + { code: 'emxdigital', gvlid: 183 }, + { code: 'pagescience' }, + { code: 'defymedia' }, + { code: 'gourmetads' }, + { code: 'matomy' }, + { code: 'featureforward' }, + { code: 'oftmedia' }, + { code: 'districtm', gvlid: 144 }, + { code: 'adasta' }, + { code: 'beintoo', gvlid: 618 }, + ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js new file mode 100644 index 00000000000..324c125f5ef --- /dev/null +++ b/modules/apstreamBidAdapter.js @@ -0,0 +1,485 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const CONSTANTS = { + DSU_KEY: 'apr_dsu', + BIDDER_CODE: 'apstream', + GVLID: 394 +}; +const storage = getStorageManager(CONSTANTS.GVLID, CONSTANTS.BIDDER_CODE); + +var dsuModule = (function() { + 'use strict'; + + var DSU_KEY = 'apr_dsu'; + var DSU_VERSION_NUMBER = '1'; + var SIGNATURE_SALT = 'YicAu6ZpNG'; + var DSU_CREATOR = {'USERREPORT': '1'}; + + function stringToU8(str) { + if (typeof TextEncoder === 'function') { + return new TextEncoder().encode(str); + } + str = unescape(encodeURIComponent(str)); + var bytes = new Uint8Array(str.length); + for (var i = 0, j = str.length; i < j; i++) { + bytes[i] = str.charCodeAt(i); + } + return bytes; + } + + function _add(a, b) { + var rl = a.l + b.l; + var a2 = { + h: a.h + b.h + (rl / 2 >>> 31) >>> 0, + l: rl >>> 0 + }; + a.h = a2.h; + a.l = a2.l; + } + function _xor(a, b) { + a.h ^= b.h; + a.h >>>= 0; + a.l ^= b.l; + a.l >>>= 0; + } + function _rotl(a, n) { + var a2 = { + h: a.h << n | a.l >>> (32 - n), + l: a.l << n | a.h >>> (32 - n) + }; + a.h = a2.h; + a.l = a2.l; + } + function _rotl32(a) { + var al = a.l; + a.l = a.h; + a.h = al; + } + + function _compress(v0, v1, v2, v3) { + _add(v0, v1); + _add(v2, v3); + _rotl(v1, 13); + _rotl(v3, 16); + _xor(v1, v0); + _xor(v3, v2); + _rotl32(v0); + _add(v2, v1); + _add(v0, v3); + _rotl(v1, 17); + _rotl(v3, 21); + _xor(v1, v2); + _xor(v3, v0); + _rotl32(v2); + } + function _getInt(a, offset) { + return a[offset + 3] << 24 | + a[offset + 2] << 16 | + a[offset + 1] << 8 | + a[offset]; + } + + function hash(key, m) { + if (typeof m === 'string') { + m = stringToU8(m); + } + var k0 = { + h: key[1] >>> 0, + l: key[0] >>> 0 + }; + var k1 = { + h: key[3] >>> 0, + l: key[2] >>> 0 + }; + var v0 = { + h: k0.h, + l: k0.l + }; + var v2 = k0; + var v1 = { + h: k1.h, + l: k1.l + }; + var v3 = k1; + var ml = m.length; + var ml7 = ml - 7; + var buf = new Uint8Array(new ArrayBuffer(8)); + _xor(v0, { + h: 0x736f6d65, + l: 0x70736575 + }); + _xor(v1, { + h: 0x646f7261, + l: 0x6e646f6d + }); + _xor(v2, { + h: 0x6c796765, + l: 0x6e657261 + }); + _xor(v3, { + h: 0x74656462, + l: 0x79746573 + }); + var mp = 0; + while (mp < ml7) { + var mi = { + h: _getInt(m, mp + 4), + l: _getInt(m, mp) + }; + _xor(v3, mi); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _xor(v0, mi); + mp += 8; + } + buf[7] = ml; + var ic = 0; + while (mp < ml) { + buf[ic++] = m[mp++]; + } + while (ic < 7) { + buf[ic++] = 0; + } + var mil = { + h: buf[7] << 24 | buf[6] << 16 | buf[5] << 8 | buf[4], + l: buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0] + }; + _xor(v3, mil); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _xor(v0, mil); + _xor(v2, { + h: 0, + l: 0xff + }); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + _compress(v0, v1, v2, v3); + var h = v0; + _xor(h, v1); + _xor(h, v2); + _xor(h, v3); + return h; + } + + function hashHex(key, m) { + var r = hash(key, m); + return ('0000000' + r.h.toString(16)).substr(-8) + + ('0000000' + r.l.toString(16)).substr(-8); + } + + var SIPHASH_KEY = [0x86395a57, 0x6b5ba7f7, 0x69732c07, 0x2a6ef48d]; + var hashWithKey = hashHex.bind(null, SIPHASH_KEY); + + var parseUrlRegex = new RegExp('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?'); + var overwrite = null; + var cache = {}; + function parseUrl(url) { + var addscheme = + url.indexOf('/') !== 0 && + url.indexOf('/') !== -1 && + (url.indexOf(':') === -1 || url.indexOf(':') > url.indexOf('/')); + + var match = parseUrlRegex.exec(addscheme ? 'noscheme://' + url : url); + var res = { + scheme: addscheme ? '' : match[2] || '', + host: match[4] || '', + hostname: match[4] ? match[4].split(':')[0] : '', + pathname: match[5] || '', + search: match[7] || '', + hash: match[9] || '', + toString: function () { + return url; + } + }; + + res.origin = res.scheme + '://' + res.host; + return res; + } + + function location() { + var url = overwrite || window.location.toString(); + url = url.replace(/\.demo\.audienceproject\.com\//, '/'); + + if (cache.url === url) { + return cache.parsed; + } + var parsed = parseUrl(url); + cache.url = url; + cache.parsed = parsed; + return parsed; + } + + function getDaysSinceApEpoch() { + var timeDiff = (new Date()).getTime() - (new Date(2019, 0, 1)).getTime(); + var daysSinceApEpoch = Math.floor(timeDiff / (1000 * 3600 * 24)); + return daysSinceApEpoch; + } + + function generateDsu() { + var dsuId = utils.generateUUID(); + var loc = location(); + + var dsuIdSuffix = hashWithKey(dsuId + loc.toString()); + var suffix4 = dsuIdSuffix.substr(0, 4); + var suffix8 = dsuIdSuffix.substr(4); + + dsuId = dsuId.substr(0, 19) + suffix4 + '-' + suffix8; + + var daysSinceApEpoch = getDaysSinceApEpoch(); + var originHash = hashWithKey(loc.origin); + + var metadata = [ + DSU_CREATOR.USERREPORT, + daysSinceApEpoch, + originHash + ].join('.'); + var signature = hashWithKey(dsuId + metadata + SIGNATURE_SALT); + + return [DSU_VERSION_NUMBER, signature, dsuId, metadata].join('.'); + } + + function readOrCreateDsu() { + var dsu; + try { + dsu = storage.getDataFromLocalStorage(DSU_KEY); + } catch (err) { + return null; + } + + if (!dsu) { + dsu = generateDsu(); + } + + try { + storage.setDataInLocalStorage(DSU_KEY, dsu); + } catch (err) { + return null; + } + + return dsu; + } + + return { + readOrCreateDsu: readOrCreateDsu + } +})(); + +function serializeSizes(sizes) { + if (Array.isArray(sizes[0]) === false) { + sizes = [sizes]; + } + + return sizes.map(s => s[0] + 'x' + s[1]).join('_'); +} + +function getRawConsentString(gdprConsentConfig) { + if (!gdprConsentConfig || gdprConsentConfig.gdprApplies === false) { + return null; + } + + return gdprConsentConfig.consentString; +} + +function getConsentStringFromPrebid(gdprConsentConfig) { + const consentString = getRawConsentString(gdprConsentConfig); + if (!consentString) { + return null; + } + + let isIab = config.getConfig('consentManagement.cmpApi') != 'static'; + let vendorConsents = ( + gdprConsentConfig.vendorData.vendorConsents || + (gdprConsentConfig.vendorData.vendor || {}).consents || + {} + ); + let isConsentGiven = !!vendorConsents[CONSTANTS.GVLID.toString(10)]; + + return isIab && isConsentGiven ? consentString : null; +} + +function getIabConsentString(bidderRequest) { + if (utils.deepAccess(bidderRequest, 'gdprConsent')) { + return getConsentStringFromPrebid(bidderRequest.gdprConsent); + } + + return 'disabled'; +} + +function injectPixels(ad, pixels, scripts) { + if (!pixels && !scripts) { + return ad; + } + + let trackedAd = ad; + if (pixels) { + pixels.forEach(pixel => { + const tracker = utils.createTrackPixelHtml(pixel); + trackedAd += tracker; + }); + } + + if (scripts) { + scripts.forEach(script => { + const tracker = ``; + trackedAd += tracker; + }); + } + + return trackedAd; +} + +function getScreenParams() { + return `${window.screen.width}x${window.screen.height}@${window.devicePixelRatio}`; +} + +function getBids(bids) { + const bidArr = bids.map(bid => { + const bidId = bid.bidId; + + let mediaType = ''; + const mediaTypes = Object.keys(bid.mediaTypes) + switch (mediaTypes[0]) { + case 'video': + mediaType = 'v'; + break; + + case 'native': + mediaType = 'n'; + break; + + case 'audio': + mediaType = 'a'; + break; + + default: + mediaType = 'b'; + break; + } + + let adUnitCode = `,c=${bid.adUnitCode}`; + if (bid.params.code) { + adUnitCode = `,c=${encodeURIComponent(bid.params.code)}`; + } + if (bid.params.adunitId) { + adUnitCode = `,u=${encodeURIComponent(bid.params.adunitId)}`; + } + + return `${bidId}:t=${mediaType},s=${serializeSizes(bid.sizes)}${adUnitCode}`; + }); + + return bidArr.join(';'); +}; + +function getEndpointsGroups(bidRequests) { + let endpoints = []; + const getEndpoint = bid => { + if (bid.params.test) { + return `https://mock-bapi.userreport.com/v2/${bid.params.publisherId}/bid`; + } + + if (bid.params.endpoint) { + return `${bid.params.endpoint}${bid.params.publisherId}/bid`; + } + + return `https://bapi.userreport.com/v2/${bid.params.publisherId}/bid`; + } + bidRequests.forEach(bid => { + const exist = endpoints.filter(item => item.endpoint.indexOf(bid.params.endpoint) > -1)[0]; + if (exist) { + exist.bids.push(bid); + } else { + endpoints.push({ + endpoint: getEndpoint(bid), + bids: [bid] + }); + } + }); + + return endpoints; +} + +function isBidRequestValid(bid) { + const isPublisherIdExist = !!bid.params.publisherId; + const isOneMediaType = Object.keys(bid.mediaTypes).length === 1; + + return isPublisherIdExist && isOneMediaType; +} + +function buildRequests(bidRequests, bidderRequest) { + const data = { + med: encodeURIComponent(window.location.href), + auid: bidderRequest.auctionId, + ref: document.referrer, + dnt: utils.getDNT() ? 1 : 0, + sr: getScreenParams() + }; + + const consentData = getRawConsentString(bidderRequest.gdprConsent); + data.iab_consent = consentData; + + const options = { + withCredentials: true + }; + + const isConsent = getIabConsentString(bidderRequest); + const noDsu = config.getConfig('apstream.noDsu'); + if (!isConsent || noDsu) { + data.dsu = ''; + } else { + data.dsu = dsuModule.readOrCreateDsu(); + } + + if (!isConsent || isConsent === 'disabled') { + options.withCredentials = false; + } + + const endpoints = getEndpointsGroups(bidRequests); + const serverRequests = endpoints.map(item => ({ + method: 'GET', + url: item.endpoint, + data: { + ...data, + bids: getBids(item.bids), + rnd: Math.random() + }, + options: options + })); + + return serverRequests; +} + +function interpretResponse(serverResponse) { + let bidResponses = serverResponse && serverResponse.body; + + if (!bidResponses || !bidResponses.length) { + return []; + } + + return bidResponses.map(x => ({ + requestId: x.bidId, + cpm: x.bidDetails.cpm, + width: x.bidDetails.width, + height: x.bidDetails.height, + creativeId: x.bidDetails.creativeId, + currency: x.bidDetails.currency || 'USD', + netRevenue: x.bidDetails.netRevenue, + dealId: x.bidDetails.dealId, + ad: injectPixels(x.bidDetails.ad, x.bidDetails.noticeUrls, x.bidDetails.impressionScripts), + ttl: x.bidDetails.ttl, + })); +} + +export const spec = { + code: CONSTANTS.BIDDER_CODE, + gvlid: CONSTANTS.GVLID, + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse +} + +registerBidder(spec); diff --git a/modules/apstreamBidAdapter.md b/modules/apstreamBidAdapter.md new file mode 100644 index 00000000000..e528307a003 --- /dev/null +++ b/modules/apstreamBidAdapter.md @@ -0,0 +1,97 @@ +# Overview + +``` +Module Name: AP Stream Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@audienceproject.com +gdpr_supported: true +tcf2_supported: true +``` + +# Description + +Module that connects to AP Stream source + +# Inherit from prebid.js +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID // mandatory + } + }] + } + ]; +``` + +# Explicit ad-unit code +``` + var website = null; + switch (location.hostname) { + case "site1.com": + website = "S1"; + break; + case "site2.com": + website = "S2"; + break; + } + + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID, // mandatory + code: website + '_Leaderboard' + } + }] + } + ]; +``` + +# Explicit ad-unit ID +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { // mandatory and should be only one + banner: { + sizes: [[920,180], [920, 130]] + } + }, + bids: [{ + bidder: 'apstream', + params: { + publisherId: STREAM_PIBLISHER_ID, // mandatory + adunitId: 1234 + } + }] + } + ]; +``` + +# DSU + +To disable DSU use config option: + +``` + pbjs.setConfig({ + apstream: { + noDsu: true + } + }); +``` diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js deleted file mode 100644 index 816a6abd0e8..00000000000 --- a/modules/audienceNetworkBidAdapter.js +++ /dev/null @@ -1,308 +0,0 @@ -/** - * @file AudienceNetwork adapter. - */ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { generateUUID, deepAccess, convertTypes, formatQS } from '../src/utils.js'; -import findIndex from 'core-js-pure/features/array/find-index.js'; -import includes from 'core-js-pure/features/array/includes.js'; - -const code = 'audienceNetwork'; -const currency = 'USD'; -const method = 'GET'; -const url = 'https://an.facebook.com/v2/placementbid.json'; -const supportedMediaTypes = ['banner', 'video']; -const netRevenue = true; -const hbBidder = 'fan'; -const ttl = 600; -const videoTtl = 3600; -const platver = '$prebid.version$'; -const platform = '241394079772386'; -const adapterver = '1.3.0'; - -/** - * Does this bid request contain valid parameters? - * @param {Object} bid - * @returns {Boolean} - */ -const isBidRequestValid = bid => - typeof bid.params === 'object' && - typeof bid.params.placementId === 'string' && - bid.params.placementId.length > 0 && - Array.isArray(bid.sizes) && bid.sizes.length > 0 && - (isFullWidth(bid.params.format) ? bid.sizes.map(flattenSize).some(size => size === '300x250') : true) && - (isValidNonSizedFormat(bid.params.format) || bid.sizes.map(flattenSize).some(isValidSize)); - -/** - * Flattens a 2-element [W, H] array as a 'WxH' string, - * otherwise passes value through. - * @param {Array|String} size - * @returns {String} - */ -const flattenSize = size => - (Array.isArray(size) && size.length === 2) ? `${size[0]}x${size[1]}` : size; - -/** - * Expands a 'WxH' string as a 2-element [W, H] array - * @param {String} size - * @returns {Array} - */ -const expandSize = size => size.split('x').map(Number); - -/** - * Is this a valid slot size? - * @param {String} size - * @returns {Boolean} - */ -const isValidSize = size => includes(['300x250', '320x50'], size); - -/** - * Is this a valid, non-sized format? - * @param {String} size - * @returns {Boolean} - */ -const isValidNonSizedFormat = format => includes(['video', 'native'], format); - -/** - * Is this a valid size and format? - * @param {String} size - * @returns {Boolean} - */ -const isValidSizeAndFormat = (size, format) => - (isFullWidth(format) && flattenSize(size) === '300x250') || - isValidNonSizedFormat(format) || - isValidSize(flattenSize(size)); - -/** - * Find a preferred entry, if any, from an array of valid sizes. - * @param {Array} acc - * @param {String} cur - */ -const sortByPreferredSize = (acc, cur) => - (cur === '300x250') ? [cur, ...acc] : [...acc, cur]; - -/** - * Map any deprecated size/formats to new values. - * @param {String} size - * @param {String} format - */ -const mapDeprecatedSizeAndFormat = (size, format) => - isFullWidth(format) ? ['300x250', null] : [size, format]; - -/** - * Is this a video format? - * @param {String} format - * @returns {Boolean} - */ -const isVideo = format => format === 'video'; - -/** - * Is this a fullwidth format? - * @param {String} format - * @returns {Boolean} - */ -const isFullWidth = format => format === 'fullwidth'; - -/** - * Which SDK version should be used for this format? - * @param {String} format - * @returns {String} - */ -const sdkVersion = format => isVideo(format) ? '' : '6.0.web'; - -/** - * Which platform identifier should be used? - * @param {Array} platforms Possible platform identifiers - * @returns {String} First valid platform found, or default if none found - */ -const findPlatform = platforms => [...platforms.filter(Boolean), platform][0]; - -/** - * Does the search part of the URL contain "anhb_testmode" - * and therefore indicate testmode should be used? - * @returns {String} "true" or "false" - */ -const isTestmode = () => Boolean( - window && window.location && - typeof window.location.search === 'string' && - window.location.search.indexOf('anhb_testmode') !== -1 -).toString(); - -/** - * Generate ad HTML for injection into an iframe - * @param {String} placementId - * @param {String} format - * @param {String} bidId - * @returns {String} HTML - */ -const createAdHtml = (placementId, format, bidId) => { - const nativeStyle = format === 'native' ? '' : ''; - const nativeContainer = format === 'native' ? '
' : ''; - return ` - ${nativeStyle} - -
- - - ${nativeContainer} -
- -`; -}; - -/** - * Convert each bid request to a single URL to fetch those bids. - * @param {Array} bids - list of bids - * @param {String} bids[].placementCode - Prebid placement identifier - * @param {Object} bids[].params - * @param {String} bids[].params.placementId - Audience Network placement identifier - * @param {String} bids[].params.platform - Audience Network platform identifier (optional) - * @param {String} bids[].params.format - Optional format, one of 'video' or 'native' if set - * @param {Array} bids[].sizes - list of desired advert sizes - * @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50] - * @returns {Array} List of URLs to fetch, plus formats and sizes for later use with interpretResponse - */ -const buildRequests = (bids, bidderRequest) => { - // Build lists of placementids, adformats, sizes and SDK versions - const placementids = []; - const adformats = []; - const sizes = []; - const sdk = []; - const platforms = []; - const requestIds = []; - - bids.forEach(bid => bid.sizes - .map(flattenSize) - .filter(size => isValidSizeAndFormat(size, bid.params.format)) - .reduce(sortByPreferredSize, []) - .slice(0, 1) - .forEach(preferredSize => { - const [size, format] = mapDeprecatedSizeAndFormat(preferredSize, bid.params.format); - placementids.push(bid.params.placementId); - adformats.push(format || size); - sizes.push(size); - sdk.push(sdkVersion(format)); - platforms.push(bid.params.platform); - requestIds.push(bid.bidId); - }) - ); - // Build URL - const testmode = isTestmode(); - const pageurl = encodeURIComponent(deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || deepAccess(bidderRequest, 'refererInfo.referer')); - const platform = findPlatform(platforms); - const cb = generateUUID(); - const search = { - placementids, - adformats, - testmode, - pageurl, - sdk, - adapterver, - platform, - platver, - cb - }; - const video = findIndex(adformats, isVideo); - if (video !== -1) { - [search.playerwidth, search.playerheight] = expandSize(sizes[video]); - } - const data = formatQS(search); - - return [{ adformats, data, method, requestIds, sizes, url, pageurl }]; -}; - -/** - * Convert a server response to a bid response. - * @param {Object} response - object representing the response - * @param {Object} response.body - response body, already converted from JSON - * @param {Object} bidRequests - the original bid requests - * @param {Array} bidRequest.adformats - list of formats for the original bid requests - * @param {Array} bidRequest.sizes - list of sizes fot the original bid requests - * @returns {Array} A list of bid response objects - */ -const interpretResponse = ({ body }, { adformats, requestIds, sizes, pageurl }) => { - const { bids = {} } = body; - return Object.keys(bids) - // extract Array of bid responses - .map(placementId => bids[placementId]) - // flatten - .reduce((a, b) => a.concat(b), []) - // transform to bidResponse - .map((bid, i) => { - const { - bid_id: fbBidid, - placement_id: creativeId, - bid_price_cents: cpm - } = bid; - - const format = adformats[i]; - const [width, height] = expandSize(flattenSize(sizes[i])); - const ad = createAdHtml(creativeId, format, fbBidid); - const requestId = requestIds[i]; - - const bidResponse = { - // Prebid attributes - requestId, - cpm: cpm / 100, - width, - height, - ad, - ttl, - creativeId, - netRevenue, - currency, - // Audience Network attributes - hb_bidder: hbBidder, - fb_bidid: fbBidid, - fb_format: format, - fb_placementid: creativeId - }; - // Video attributes - if (isVideo(format)) { - bidResponse.mediaType = 'video'; - bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fbBidid}`; - bidResponse.ttl = videoTtl; - } - return bidResponse; - }); -}; - -/** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ -const transformBidParams = (params, isOpenRtb) => { - return convertTypes({ - 'placementId': 'string' - }, params); -} - -export const spec = { - code, - supportedMediaTypes, - isBidRequestValid, - buildRequests, - interpretResponse, - transformBidParams -}; - -registerBidder(spec); diff --git a/modules/audienceNetworkBidAdapter.md b/modules/audienceNetworkBidAdapter.md deleted file mode 100644 index 6147191f4b7..00000000000 --- a/modules/audienceNetworkBidAdapter.md +++ /dev/null @@ -1,49 +0,0 @@ -# Overview - -Module Name: Audience Network Bid Adapter - -Module Type: Bidder Adapter - -Maintainer: Lovell Fuller - -# Parameters - -| Name | Scope | Description | Example | -| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | -| `placementId` | required | The Placement ID from Audience Network | "555555555555555\_555555555555555" | -| `format` | optional | Format, one of "native" or "video" | "native" | - -# Example ad units - -```javascript -const adUnits = [{ - code: "test-iab", - sizes: [[300, 250]], - bids: [{ - bidder: "audienceNetwork", - params: { - placementId: "555555555555555_555555555555555" - } - }] -}, { - code: "test-native", - sizes: [[300, 250]], - bids: [{ - bidder: "audienceNetwork", - params: { - format: "native", - placementId: "555555555555555_555555555555555" - } - }] -}, { - code: "test-video", - sizes: [[640, 360]], - bids: [{ - bidder: "audienceNetwork", - params: { - format: "video", - placementId: "555555555555555_555555555555555" - } - }] -}]; -``` diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 95d225cb5f7..414cadcd405 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -29,15 +29,18 @@ export const spec = { const siteId = validBidRequests[0].params.siteId const placementId = validBidRequests[0].params.placementId - const impressions = validBidRequests.map(bidRequest => ({ - id: bidRequest.bidId, - banner: { - format: bidRequest.sizes.map(sizeArr => ({ - w: sizeArr[0], - h: sizeArr[1], - })) - }, - })) + const impressions = validBidRequests.map(bidRequest => { + return { + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1], + })) + }, + } + }) // params from bid request const openrtbRequest = { diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js new file mode 100644 index 00000000000..04f4085ba24 --- /dev/null +++ b/modules/boldwinBidAdapter.js @@ -0,0 +1,110 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'boldwin'; +const AD_URL = 'https://ssp.videowalldirect.com/?c=o&m=multi'; +const SYNC_URL = 'https://cs.videowalldirect.com/?c=o&m=cookie' + +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.title && bid.native.image && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let 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++) { + let bid = validBidRequests[i]; + let sizes + if (bid.mediaTypes) { + if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + sizes = bid.mediaTypes[BANNER].sizes + } else if (bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + sizes = bid.mediaTypes[VIDEO].playerSize + } + } + placements.push({ + placementId: bid.params.placementId, + bidId: bid.bidId, + sizes: sizes || [], + wPlayer: sizes ? sizes[0] : 0, + hPlayer: sizes ? sizes[1] : 0, + traffic: bid.params.traffic || BANNER, + schain: bid.schain || {} + }); + } + 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)) { + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: () => { + return [{ + type: 'image', + url: SYNC_URL + }]; + } +}; + +registerBidder(spec); diff --git a/modules/boldwinBidAdapter.md b/modules/boldwinBidAdapter.md new file mode 100644 index 00000000000..4bf272c4de3 --- /dev/null +++ b/modules/boldwinBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: boldwin Bidder Adapter +Module Type: boldwin Bidder Adapter +``` + +# Description + +Module that connects to boldwin demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + placementId: 0, + traffic: 'video' + } + } + ] + } + ]; +``` diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js new file mode 100644 index 00000000000..5a285be71c0 --- /dev/null +++ b/modules/brightMountainMediaBidAdapter.js @@ -0,0 +1,89 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'brightmountainmedia'; +const AD_URL = 'https://console.brightmountainmedia.com/hb/bid'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.placement_id); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + let placements = []; + let request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL' + request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + } + } + for (let i = 0; i < validBidRequests.length; i++) { + let bid = validBidRequests[i]; + let traff = bid.params.traffic || BANNER + let placement = { + placementId: bid.params.placement_id, + bidId: bid.bidId, + sizes: bid.mediaTypes[traff].sizes, + traffic: traff + }; + if (bid.schain) { + placement.schain = bid.schain; + } + placements.push(placement); + } + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + try { + serverResponse = serverResponse.body; + for (let i = 0; i < serverResponse.length; i++) { + let resItem = serverResponse[i]; + + response.push(resItem); + } + } catch (e) { + utils.logMessage(e); + }; + return response; + }, + + getUserSyncs: (syncOptions) => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: 'https://console.brightmountainmedia.com:4444/cookieSync' + }]; + } + }, + +}; + +registerBidder(spec); diff --git a/modules/brightMountainMediaBidAdapter.md b/modules/brightMountainMediaBidAdapter.md new file mode 100644 index 00000000000..a9900b5264d --- /dev/null +++ b/modules/brightMountainMediaBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Bright Mountain Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dev@brightmountainmedia.com +``` + +# Description + +Connects to Bright Mountain Media exchange for bids. + +Bright Mountain Media bid adapter currently supports Banner. + +# Test Parameters +``` + var adUnits = [ + code: 'placementid_0', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'brightmountainmedia', + params: { + placement_id: '5f21784949be82079d08c', + traffic: 'banner' + } + } + ] + ]; +``` \ No newline at end of file diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index a4b013a2fe2..2aad211b186 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -70,6 +70,11 @@ function buildRequests(bidReqs, bidderRequest) { tmax: config.getConfig('bidderTimeout') }; + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + utils.deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + return { method: 'POST', url: URL, diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md index 89287aed7ca..a15f601aee3 100644 --- a/modules/britepoolIdSystem.md +++ b/modules/britepoolIdSystem.md @@ -7,7 +7,7 @@ BritePool User ID Module. For assistance setting up your module please contact u Individual params may be set for the BritePool User ID Submodule. At least one identifier must be set in the params. ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: 'britepoolId', storage: { diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 3dcb8da9838..04459ec1f09 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; +import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; @@ -18,8 +19,6 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { - let digitrust; - let ret = { method: 'POST', url: '', @@ -41,7 +40,8 @@ export const spec = { screensize: getScreenSize(), dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, language: navigator.language, - ua: navigator.userAgent + ua: navigator.userAgent, + pversion: '$prebid.version$' }); // coppa compliance @@ -69,80 +69,19 @@ export const spec = { utils.deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent); } - // Digitrust Support - const bidRequestDigitrust = utils.deepAccess(validBidRequests[0], 'userId.digitrustid.data'); - if (bidRequestDigitrust && (!bidRequestDigitrust.privacy || !bidRequestDigitrust.privacy.optout)) { - digitrust = { - id: bidRequestDigitrust.id, - keyv: bidRequestDigitrust.keyv - } - } - - if (digitrust) { - utils.deepSetValue(data, 'user.ext.digitrust', { - id: digitrust.id, - keyv: digitrust.keyv - }) - } - - if (validBidRequests[0].userId && typeof validBidRequests[0].userId === 'object' && (validBidRequests[0].userId.tdid || validBidRequests[0].userId.pubcid || validBidRequests[0].userId.lipb || validBidRequests[0].userId.id5id || validBidRequests[0].userId.parrableid)) { - utils.deepSetValue(data, 'user.ext.eids', []); - - if (validBidRequests[0].userId.tdid) { - data.user.ext.eids.push({ - source: 'adserver.org', - uids: [{ - id: validBidRequests[0].userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - } - - if (validBidRequests[0].userId.pubcid) { - data.user.ext.eids.push({ - source: 'pubcommon', - uids: [{ - id: validBidRequests[0].userId.pubcid, - }] - }); - } - - if (validBidRequests[0].userId.id5id) { - data.user.ext.eids.push({ - source: 'id5-sync.com', - uids: [{ - id: validBidRequests[0].userId.id5id, - }] - }); - } - - if (validBidRequests[0].userId.parrableid) { - data.user.ext.eids.push({ - source: 'parrable.com', - uids: [{ - id: validBidRequests[0].userId.parrableid, - }] - }); - } - - if (validBidRequests[0].userId.lipb && validBidRequests[0].userId.lipb.lipbid) { - data.user.ext.eids.push({ - source: 'liveintent.com', - uids: [{ - id: validBidRequests[0].userId.lipb.lipbid - }] - }); - } + // EIDS Support + if (validBidRequests[0].userId) { + data.user.ext.eids = createEidsArray(validBidRequests[0].userId); } validBidRequests.map(bid => { const placement = Object.assign({ id: bid.transactionId, divName: bid.bidId, + pisze: bid.mediaTypes.banner.sizes[0] || bid.sizes[0], sizes: bid.mediaTypes.banner.sizes, - adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes) + adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes), + floor: getBidFloor(bid) }, bid.params); if (placement.networkId && placement.siteId) { @@ -277,6 +216,22 @@ sizeMap[331] = '320x250'; sizeMap[3301] = '320x267'; sizeMap[2730] = '728x250'; +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: '*' + }); + } + + let floor = floorInfo.floor || 0; + + return floor; +} + function getSize(sizes) { const result = []; sizes.forEach(function(size) { diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 53e97006bd1..90fe24735db 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -14,9 +14,12 @@ const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; const DEFAULT_ALLOW_AUCTION_WO_CONSENT = true; +export const allowAuction = { + value: DEFAULT_ALLOW_AUCTION_WO_CONSENT, + definedInConfig: false +} export let userCMP; export let consentTimeout; -export let allowAuction; export let gdprScope; export let staticConsentData; @@ -97,7 +100,9 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function v2CmpResponseCallback(tcfData, success) { utils.logInfo('Received a response from CMP', tcfData); if (success) { - if (tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { + if (tcfData.gdprApplies === false) { + cmpSuccess(tcfData, hookConfig); + } else if (tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { cmpSuccess(tcfData, hookConfig); } else if (tcfData.eventStatus === 'cmpuishown' && tcfData.tcString && tcfData.purposeOneTreatment === true) { cmpSuccess(tcfData, hookConfig); @@ -322,6 +327,13 @@ function processCmpData(consentObject, hookConfig) { // determine which set of checks to run based on cmpVersion let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null; + // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2. + if (allowAuction.definedInConfig && cmpVersion === 2) { + utils.logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); + } else if (!allowAuction.definedInConfig && cmpVersion === 1) { + utils.logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + } + if (utils.isFn(checkFn)) { if (checkFn(consentObject)) { cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject); @@ -352,14 +364,14 @@ function cmpFailed(errMsg, hookConfig, extraArgs) { clearTimeout(hookConfig.timer); // still set the consentData to undefined when there is a problem as per config options - if (allowAuction) { + if (allowAuction.value && cmpVersion === 1) { storeConsentData(undefined); } exitModule(errMsg, hookConfig, extraArgs); } /** - * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction + * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanager.js for later in the auction * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) */ function storeConsentData(cmpConsentObject) { @@ -375,6 +387,9 @@ function storeConsentData(cmpConsentObject) { vendorData: (cmpConsentObject) || undefined, gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope }; + if (cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { + consentData.addtlConsent = cmpConsentObject.addtlConsent; + }; } consentData.apiVersion = cmpVersion; gdprDataHandler.setConsentData(consentData); @@ -406,8 +421,8 @@ function exitModule(errMsg, hookConfig, extraArgs) { let nextFn = hookConfig.nextFn; if (errMsg) { - if (allowAuction) { - utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); + if (allowAuction.value && cmpVersion === 1) { + utils.logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs); nextFn.apply(context, args); } else { utils.logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs); @@ -460,10 +475,8 @@ export function setConsentConfig(config) { } if (typeof config.allowAuctionWithoutConsent === 'boolean') { - allowAuction = config.allowAuctionWithoutConsent; - } else { - allowAuction = DEFAULT_ALLOW_AUCTION_WO_CONSENT; - utils.logInfo(`consentManagement config did not specify allowAuctionWithoutConsent. Using system default setting (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + allowAuction.value = config.allowAuctionWithoutConsent; + allowAuction.definedInConfig = true; } // if true, then gdprApplies should be set to true diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 71a8471d554..1d999fdbacc 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -82,7 +82,7 @@ export function buildDfpVideoUrl(options) { const derivedParams = { correlator: Date.now(), - sz: parseSizesInput(adUnit.sizes).join('|'), + sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'), url: encodeURIComponent(location.href), }; const encodedCustomParams = getCustParams(bid, options); diff --git a/modules/digiTrustIdSystem.js b/modules/digiTrustIdSystem.js deleted file mode 100644 index d8aa8be9376..00000000000 --- a/modules/digiTrustIdSystem.js +++ /dev/null @@ -1,460 +0,0 @@ -/** - * This module adds DigiTrust ID support to the User ID module - * The {@link module:modules/userId} module is required - * If the full DigiTrust Id library is included the standard functions - * will be invoked to obtain the user's DigiTrust Id. - * When the full library is not included this will fall back to the - * DigiTrust Identity API and generate a mock DigiTrust object. - * @module modules/digiTrustIdSystem - * @requires module:modules/userId - */ - -import * as utils from '../src/utils.js' -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const DT_VENDOR_ID = 64; // cmp gvlVendorId -const storage = getStorageManager(DT_VENDOR_ID); - -var fallbackTimeout = 1550; // timeout value that allows userId system to execute first -var fallbackTimer = 0; // timer Id for fallback init so we don't double call - -/** - * Checks to see if the DigiTrust framework is initialized. - * @function - */ -function isInitialized() { - if (window.DigiTrust == null) { - return false; - } - // eslint-disable-next-line no-undef - return DigiTrust.isClient; // this is set to true after init -} - -/** - * Tests for presence of the DigiTrust object - * */ -function isPresent() { - return (window.DigiTrust != null); -} - -var noop = function () { -}; - -const MAX_RETRIES = 2; -const DT_ID_SVC = 'https://prebid.digitru.st/id/v1'; - -var isFunc = function (fn) { - return typeof (fn) === 'function'; -} - -var _savedId = null; // closure variable for storing Id to avoid additional requests - -function callApi(options) { - var ajaxOptions = { - method: 'GET', - withCredentials: true - }; - - ajax( - DT_ID_SVC, - { - success: options.success, - error: options.fail - }, - null, - ajaxOptions - ); -} - -/** - * Encode the Id per DigiTrust lib - * @param {any} id - */ -function encId(id) { - try { - if (typeof (id) !== 'string') { - id = JSON.stringify(id); - } - return btoa(id); - } catch (ex) { - return id; - } -} - -/** - * Writes the Identity into the expected DigiTrust cookie - * @param {any} id - */ -function writeDigiId(id) { - var key = 'DigiTrust.v1.identity'; - var date = new Date(); - date.setTime(date.getTime() + 604800000); - storage.setCookie(key, encId(id), date.toUTCString(), 'none'); -} - -/** - * Tests to see if the current browser is FireFox - */ -function isFirefoxBrowser(ua) { - ua = ua || navigator.userAgent; - ua = ua.toLowerCase(); - if (ua.indexOf('firefox') !== -1) { - return true; - } - return false; -} - -/** - * Test to see if the user has a browser that is disallowed for making AJAX - * requests due to the behavior not supported DigiTrust ID Cookie. - */ -function isDisallowedBrowserForApiCall() { - if (utils.isSafariBrowser()) { - return true; - } else if (isFirefoxBrowser()) { - return true; - } - return false; -} - -/** - * Set up a DigiTrust facade object to mimic the API - * - */ -function initDigitrustFacade(config) { - clearTimeout(fallbackTimer); - fallbackTimer = 0; - - var facade = { - isClient: true, - isMock: true, - _internals: { - callCount: 0, - initCallback: null - }, - getUser: function (obj, callback) { - var isAsync = !!isFunc(callback); - var cb = isAsync ? callback : noop; - var errResp = { success: false }; - var inter = facade._internals; - inter.callCount++; - - // wrap the initializer callback, if present - var checkAndCallInitializeCb = function (idResponse) { - if (inter.callCount <= 1 && isFunc(inter.initCallback)) { - try { - inter.initCallback(idResponse); - } catch (ex) { - utils.logError('Exception in passed DigiTrust init callback', ex); - } - } - } - - if (!isMemberIdValid(obj.member)) { - if (!isAsync) { - return errResp - } else { - cb(errResp); - return; - } - } - - if (_savedId != null) { - if (isAsync) { - checkAndCallInitializeCb(_savedId); - // cb(_savedId); - return; - } else { - return _savedId; - } - } - - var opts = { - success: function (respText, result) { - var idResult = { - success: true - } - try { - idResult.identity = JSON.parse(respText); - _savedId = idResult; // Save result to the cache variable - writeDigiId(respText); - } catch (ex) { - idResult.success = false; - delete idResult.identity; - } - checkAndCallInitializeCb(idResult); - }, - fail: function (statusErr, result) { - utils.logError('DigiTrustId API error: ' + statusErr); - } - } - - // check gdpr vendor here. Full DigiTrust library has vendor check built in - gdprConsent.hasConsent(null, function (hasConsent) { - if (hasConsent) { - if (isDisallowedBrowserForApiCall()) { - let resultObj = { - success: false, - err: 'Your browser does not support DigiTrust Identity' - } - checkAndCallInitializeCb(resultObj); - return; - } - callApi(opts); - } - }) - - if (!isAsync) { - return errResp; // even if it will be successful later, without a callback we report a "failure in this moment" - } - } - } - - if (config && isFunc(config.callback)) { - facade._internals.initCallback = config.callback; - } - - if (window && window.DigiTrust == null) { - window.DigiTrust = facade; - } -} - -/** - * Tests to see if a member ID is valid within facade - * @param {any} memberId - */ -var isMemberIdValid = function (memberId) { - if (memberId && memberId.length > 0) { - return true; - } else { - utils.logError('[DigiTrust Prebid Client Error] Missing member ID, add the member ID to the function call options'); - return false; - } -}; - -/** - * DigiTrust consent handler for GDPR and __cmp. - * */ -var gdprConsent = { - hasConsent: function (options, consentCb) { - options = options || { consentTimeout: 1500 }; - var stopTimer; - var processed = false; - var consentAnswer = false; - if (typeof (window.__cmp) !== 'undefined') { - stopTimer = setTimeout(function () { - consentAnswer = false; - processed = true; - consentCb(consentAnswer); - }, options.consentTimeout); - - window.__cmp('ping', null, function(pingAnswer) { - if (pingAnswer.gdprAppliesGlobally) { - window.__cmp('getVendorConsents', [DT_VENDOR_ID], function (result) { - if (processed) { return; } // timeout before cmp answer, cancel - clearTimeout(stopTimer); - var myconsent = result.vendorConsents[DT_VENDOR_ID]; - consentCb(myconsent); - }); - } else { - if (processed) { return; } // timeout before cmp answer, cancel - clearTimeout(stopTimer); - consentAnswer = true; - consentCb(consentAnswer); - } - }); - } else { - // __cmp library is not preset. - // ignore this check and rely on id system GDPR consent management - consentAnswer = true; - consentCb(consentAnswer); - } - } -} - -/** - * Encapsulation of needed info for the callback return. - * - * @param {any} opts - */ -var ResultWrapper = function (opts) { - var me = this; - this.idObj = null; - - var idSystemFn = null; - - /** - * Callback method that is passed back to the userId module. - * - * @param {function} callback - */ - this.userIdCallback = function (callback) { - idSystemFn = callback; - if (me.idObj == null) { - me.idObj = _savedId; - } - - if (me.idObj != null && isFunc(callback)) { - callback(wrapIdResult()); - } - } - - /** - * Return a wrapped result formatted for userId system - */ - function wrapIdResult() { - if (me.idObj == null) { - me.idObj = _savedId; - } - - if (me.idObj == null) { - return null; - } - - var cp = me.configParams; - var exp = (cp && cp.storage && cp.storage.expires) || 60; - - var rslt = { - data: null, - expires: exp - }; - if (me.idObj && me.idObj.success && me.idObj.identity) { - rslt.data = me.idObj.identity; - } else { - rslt.err = 'Failure getting id'; - } - - return rslt; - } - - this.retries = 0; - this.retryId = 0; - - this.executeIdRequest = function (configParams) { - // eslint-disable-next-line no-undef - DigiTrust.getUser({ member: 'prebid' }, function (idResult) { - me.idObj = idResult; - var cb = function () { - if (isFunc(idSystemFn)) { - idSystemFn(wrapIdResult()); - } - } - - cb(); - if (configParams && configParams.callback && isFunc(configParams.callback)) { - try { - configParams.callback(idResult); - } catch (ex) { - utils.logError('Failure in DigiTrust executeIdRequest', ex); - } - } - }); - } -} - -// An instance of the result wrapper object. -var resultHandler = new ResultWrapper(); - -/* - * Internal implementation to get the Id and trigger callback - */ -function getDigiTrustId(configParams) { - if (resultHandler.configParams == null) { - resultHandler.configParams = configParams; - } - - // First see if we should initialize DigiTrust framework - if (isPresent() && !isInitialized()) { - initializeDigiTrust(configParams); - resultHandler.retryId = setTimeout(function () { - getDigiTrustId(configParams); - }, 100 * (1 + resultHandler.retries++)); - return resultHandler.userIdCallback; - } else if (!isInitialized()) { // Second see if we should build a facade object - if (resultHandler.retries >= MAX_RETRIES) { - initDigitrustFacade(configParams); // initialize a facade object that relies on the AJAX call - resultHandler.executeIdRequest(configParams); - } else { - // use expanding envelope - if (resultHandler.retryId != 0) { - clearTimeout(resultHandler.retryId); - } - resultHandler.retryId = setTimeout(function () { - getDigiTrustId(configParams); - }, 100 * (1 + resultHandler.retries++)); - } - return resultHandler.userIdCallback; - } else { // Third get the ID - resultHandler.executeIdRequest(configParams); - return resultHandler.userIdCallback; - } -} - -function initializeDigiTrust(config) { - utils.logInfo('Digitrust Init'); - var dt = window.DigiTrust; - if (dt && !dt.isClient && config != null) { - dt.initialize(config.init, config.callback); - } else if (dt == null) { - // Assume we are already on a delay and DigiTrust is not on page - initDigitrustFacade(config); - } -} - -var testHook = {}; - -/** - * Exposes the test hook object by attaching to the digitrustIdModule. - * This method is called in the unit tests to surface internals. - */ -export function surfaceTestHook() { - digiTrustIdSubmodule['_testHook'] = testHook; - return testHook; -} - -testHook.initDigitrustFacade = initDigitrustFacade; // expose for unit tests -testHook.gdpr = gdprConsent; - -/** @type {Submodule} */ -export const digiTrustIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: 'digitrust', - /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{pubcid:string}} - */ - decode: function (idData) { - try { - return { 'digitrustid': idData }; - } catch (e) { - utils.logError('DigiTrust ID submodule decode error'); - } - }, - getId: function (configParams) { - return {callback: getDigiTrustId(configParams)}; - }, - _testInit: surfaceTestHook -}; - -// check for fallback init of DigiTrust -function fallbackInit() { - if (resultHandler.retryId == 0 && !isInitialized()) { - // this triggers an init - var conf = { - member: 'fallback', - callback: noop - }; - getDigiTrustId(conf); - } -} - -fallbackTimer = setTimeout(fallbackInit, fallbackTimeout); - -submodule('userId', digiTrustIdSubmodule); diff --git a/modules/digiTrustIdSystem.md b/modules/digiTrustIdSystem.md deleted file mode 100644 index c0b274d3292..00000000000 --- a/modules/digiTrustIdSystem.md +++ /dev/null @@ -1,156 +0,0 @@ -## DigiTrust Universal Id Integration - -Setup ------ -The DigiTrust Id integration for Prebid may be used with or without the full -DigiTrust library. This is an optional module that must be used in conjunction -with the userId module. - -See the [Prebid Integration Guide for DigiTrust](https://github.com/digi-trust/dt-cdn/wiki/Prebid-Integration-for-DigiTrust-Id) -and the [DigiTrust wiki](https://github.com/digi-trust/dt-cdn/wiki) -for further instructions. - - -## Example Prebid Configuration for Digitrust Id -``` - pbjs.que.push(function() { - pbjs.setConfig({ - usersync: { - userIds: [{ - name: "digitrust", - params: { - init: { - member: 'example_member_id', - site: 'example_site_id' - }, - callback: function (digiTrustResult) { - // This callback method is optional and used for error handling - // in many if not most cases. - /* - if (digiTrustResult.success) { - // Success in Digitrust init; - // 'DigiTrust Id (encrypted): ' + digiTrustResult.identity.id; - } - else { - // Digitrust init failed - } - */ - } - }, - storage: { - type: "html5", - name: "pbjsdigitrust", - expires: 60 - } - }] - } - }); - pbjs.addAdUnits(adUnits); - pbjs.requestBids({ - bidsBackHandler: sendAdserverRequest - }); - }); - -``` - - -## Building Prebid with DigiTrust Support -Your Prebid build must include the modules for both **userId** and **digitrustIdLoader**. Follow the build instructions for Prebid as -explained in the top level README.md file of the Prebid source tree. - -ex: $ gulp build --modules=userId,digitrustIdLoader - -### Step by step Prebid build instructions for DigiTrust - -1. Download the Prebid source from [Prebid Git Repo](https://github.com/prebid/Prebid.js) -2. Set up your environment as outlined in the [Readme File](https://github.com/prebid/Prebid.js/blob/master/README.md#Build) -3. Execute the build command either with all modules or with the `userId` and `digitrustIdLoader` modules. - ``` - $ gulp build --modules=userId,digitrustIdLoader - ``` -4. (Optional) Concatenate the DigiTrust source code to the end of your `prebid.js` file for a single source distribution. -5. Upload the resulting source file to your CDN. - - -## Deploying Prebid with DigiTrust ID support -**Precondition:** You must be a DigiTrust member and have registered through the [DigiTrust Signup Process](http://www.digitru.st/signup/). -Your assigned publisher ID will be required in the configuration settings for all deployment scenarios. - -There are three supported approaches to deploying the Prebid-integrated DigiTrust package: - -* "Bare bones" deployment using only the integrated DigiTrust module code. -* Full DigiTrust with CDN referenced DigiTrust.js library. -* Full DigiTrust packaged with Prebid or site js. - -### Bare Bones Deployment - -This deployment results in the smallest Javascript package and is the simplest deployment. -It is appropriate for testing or deployments where simplicity is key. This approach -utilizes the REST API for ID generation. While there is less Javascript in use, -the user may experience more network requests than the scenarios that include the full -DigiTrust library. - -1. Build your Prebid package as above, skipping step 4. -2. Add the DigiTrust initializer section to your Prebid initialization object as below, - using your Member ID and Site ID. -3. Add a reference to your Prebid package and the initialization code on all pages you wish - to utilize Prebid with integrated DigiTrust ID. - - - - -### Full DigiTrust with CDN referenced DigiTrust library - -Both "Full DigiTrust" deployments will result in a larger initial Javascript payload. -The end user may experience fewer overall network requests as the encrypted and anonymous -DigiTrust ID can often be generated fully in client-side code. Utilizing the CDN reference -to the official DigiTrust distribution insures you will be running the latest version of the library. - -The Full DigiTrust deployment is designed to work with both new DigiTrust with Prebid deployments, and with -Prebid deployments by existing DigiTrust members. This allows you to migrate your code more slowly -without losing DigiTrust support in the process. - -1. Deploy your built copy of `prebid.js` to your CDN. -2. On each page reference both your `prebid.js` and a copy of the **DigiTrust** library. - This may either be a copy downloaded from the [DigiTrust CDN](https://cdn.digitru.st/prod/1/digitrust.min.js) to your CDN, - or directly referenced from the URL https://cdn.digitru.st/prod/1/digitrust.min.js. These may be added to the page in any order. -3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. - -### Full DigiTrust packaged with Prebid - - -1. Deploy your built copy of `prebid.js` to your CDN. Be sure to perform *Step 4* of the build to concatenate or - integrate the full DigiTrust library code with your Prebid package. -2. On each page reference your `prebid.js` -3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. - This code may also be appended to your Prebid package or placed in other initialization methods. - - - -## Parameter Descriptions for the `usersync` Configuration Section -The below parameters apply only to the DigiTrust ID integration. - -| Param under usersync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the DigiTrust module - `"digitrust"` | `"digitrust"` | -| params | Required | Object | Details for DigiTrust initialization. | | -| params.init | Required | Object | Initialization parameters, including the DigiTrust Publisher ID and Site ID. | | -| params.init.member | Required | String | DigiTrust Publisher Id | "A897dTzB" | -| params.init.site | Required | String | DigiTrust Site Id | "MM2123" | -| params.callback | Optional | Function | Callback method to fire after initialization of the DigiTrust framework. The argument indicates failure and success and the identity object upon success. | | -| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | -| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"pbjsdigitrust"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Default is 30 for UnifiedId and 1825 for PubCommonID | `365` | -| value | Optional | Object | Used only if the page has a separate mechanism for storing the Unified ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3"}` | - - - -## Further Reading - -+ [DigiTrust Home Page](http://digitru.st) - -+ [DigiTrust integration guide](https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide) - -+ [DigiTrust ID Encryption](https://github.com/digi-trust/dt-cdn/wiki/ID-encryption) - diff --git a/modules/docereeBidAdapter.md b/modules/docereeBidAdapter.md new file mode 100644 index 00000000000..9e3d4bbe1b8 --- /dev/null +++ b/modules/docereeBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: Doceree Bidder Adapter +Module Type: Bidder Adapter + +# Description + +Connects to Doceree demand source to fetch bids. +Please use ```doceree``` as the bidder code. + +# Test Parameters +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "doceree", + params: { + accountID: '167283', + zoneID: '445501', + domain: 'adbserver.doceree.com', + extra: { + tuid: '1234-abcd' + } + } + } + ] + }, + ]; +``` diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index 6688d15d8e9..fa58481548a 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -162,6 +162,7 @@ export const emxAdapter = { export const spec = { code: BIDDER_CODE, + gvlid: 183, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { if (!bid || !bid.params) { @@ -279,12 +280,21 @@ export const spec = { } return emxBidResponses; }, - getUserSyncs: function (syncOptions) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; if (syncOptions.iframeEnabled) { + let url = 'https://biddr.brealtime.com/check.html'; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + url += `?gdpr_consent=${gdprConsent.consentString}`; + } + } syncs.push({ type: 'iframe', - url: 'https://biddr.brealtime.com/check.html' + url: url }); } return syncs; diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 5e3717ce1fe..dce678362cb 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -72,7 +72,7 @@ function getPricing(xmlNode) { price: priceNode.textContent || priceNode.innerText }; } else { - utils.logWarn('PREBID - ' + BIDDER_CODE + ': Can\'t get pricing data. Is price awareness enabled?'); + utils.logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.'); } return princingData; diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 6a3fbdce1f2..97eaedd92be 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -11,39 +11,134 @@ import includes from 'core-js-pure/features/array/includes.js'; import { registerSyncInner } from '../src/adapters/bidderFactory.js'; import { getHook } from '../src/hook.js'; import { validateStorageEnforcement } from '../src/storageManager.js'; +import events from '../src/events.js'; +import { EVENTS } from '../src/constants.json'; -const purpose1 = 'storage'; +const TCF2 = { + 'purpose1': { id: 1, name: 'storage' }, + 'purpose2': { id: 2, name: 'basicAds' }, + 'purpose7': { id: 7, name: 'measurement' } +} + +/* + These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher. +*/ +const DEFAULT_RULES = [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] +}, { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] +}]; + +export let purpose1Rule; +export let purpose2Rule; +export let purpose7Rule; + +export let enforcementRules; + +const storageBlocked = []; +const biddersBlocked = []; +const analyticsBlocked = []; let addedDeviceAccessHook = false; -let enforcementRules; -function getGvlid() { +/** + * Returns gvlId for Bid Adapters. If a bidder does not have an associated gvlId, it returns 'undefined'. + * @param {string=} bidderCode - The 'code' property on the Bidder spec. + * @retuns {number} gvlId + */ +function getGvlid(bidderCode) { let gvlid; - const bidderCode = config.getCurrentBidder(); + bidderCode = bidderCode || config.getCurrentBidder(); if (bidderCode) { - const bidder = adapterManager.getBidAdapter(bidderCode); - gvlid = bidder.getSpec().gvlid; - } else { - utils.logWarn('Current module not found'); + const gvlMapping = config.getConfig('gvlMapping'); + if (gvlMapping && gvlMapping[bidderCode]) { + gvlid = gvlMapping[bidderCode]; + } else { + const bidder = adapterManager.getBidAdapter(bidderCode); + if (bidder && bidder.getSpec) { + gvlid = bidder.getSpec().gvlid; + } + } } return gvlid; } /** - * This function takes in rules and consentData as input and validates against the consentData provided. If it returns true Prebid will allow the next call else it will log a warning - * @param {Object} rules enforcement rules set in config - * @param {Object} consentData gdpr consent data + * Returns gvlId for userId module. If a userId modules does not have an associated gvlId, it returns 'undefined'. + * @param {Object} userIdModule + * @retuns {number} gvlId + */ +function getGvlidForUserIdModule(userIdModule) { + let gvlId; + const gvlMapping = config.getConfig('gvlMapping'); + if (gvlMapping && gvlMapping[userIdModule.name]) { + gvlId = gvlMapping[userIdModule.name]; + } else { + gvlId = userIdModule.gvlid; + } + return gvlId; +} + +/** + * Returns gvlId for analytics adapters. If a analytics adapter does not have an associated gvlId, it returns 'undefined'. + * @param {string} code - 'provider' property on the analytics adapter config + * @returns {number} gvlId + */ +function getGvlidForAnalyticsAdapter(code) { + let gvlId; + const gvlMapping = config.getConfig('gvlMapping'); + if (gvlMapping && gvlMapping[code]) { + gvlId = gvlMapping[code]; + } else { + gvlId = adapterManager.getAnalyticsAdapter(code).gvlid; + } + return gvlId; +} + +/** + * This function takes in a rule and consentData and validates against the consentData provided. Depending on what it returns, + * the caller may decide to suppress a TCF-sensitive activity. + * @param {Object} rule - enforcement rules set in config + * @param {Object} consentData - gdpr consent data + * @param {string=} currentModule - Bidder code of the current module + * @param {number=} gvlId - GVL ID for the module * @returns {boolean} */ -function validateRules(rule, consentData, currentModule, gvlid) { - // if vendor has exception => always true +export function validateRules(rule, consentData, currentModule, gvlId) { + const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id; + + // return 'true' if vendor present in 'vendorExceptions' if (includes(rule.vendorExceptions || [], currentModule)) { return true; } - // if enforcePurpose is false or purpose was granted isAllowed is true, otherwise false - const purposeAllowed = rule.enforcePurpose === false || utils.deepAccess(consentData, 'vendorData.purpose.consents.1') === true; - // if enforceVendor is false or vendor was granted isAllowed is true, otherwise false - const vendorAllowed = rule.enforceVendor === false || utils.deepAccess(consentData, `vendorData.vendor.consents.${gvlid}`) === true; + + // get data from the consent string + const purposeConsent = utils.deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`); + const vendorConsent = utils.deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); + const liTransparency = utils.deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`); + + /* + Since vendor exceptions have already been handled, the purpose as a whole is allowed if it's not being enforced + or the user has consented. Similar with vendors. + */ + const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true; + const vendorAllowed = rule.enforceVendor === false || vendorConsent === true; + + /* + Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming + LI for Basic Ads (Purpose 2). Prebid.js can't check to see who's declaring what legal basis, so if LI has been + established for Purpose 2, allow the auction to take place and let the server sort out the legal basis calculation. + */ + if (purposeId === 2) { + return (purposeAllowed && vendorAllowed) || (liTransparency === true); + } + return purposeAllowed && vendorAllowed; } @@ -65,22 +160,26 @@ export function deviceAccessHook(fn, gvlid, moduleName, result) { const consentData = gdprDataHandler.getConsentData(); if (consentData && consentData.gdprApplies) { if (consentData.apiVersion === 2) { - if (!gvlid) { - gvlid = getGvlid(); + const curBidder = config.getCurrentBidder(); + // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder + if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { + gvlid = getGvlid(curBidder); + } else { + gvlid = getGvlid(moduleName); } - const curModule = moduleName || config.getCurrentBidder(); - const purpose1Rule = find(enforcementRules, hasPurpose1); + const curModule = moduleName || curBidder; let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid); if (isAllowed) { result.valid = true; fn.call(this, gvlid, moduleName, result); } else { - utils.logWarn(`User denied Permission for Device access for ${curModule}`); + curModule && utils.logWarn(`TCF2 denied device access for ${curModule}`); result.valid = false; + storageBlocked.push(curModule); fn.call(this, gvlid, moduleName, result); } } else { - utils.logInfo('Enforcing TCF2 only'); + // The module doesn't enforce TCF1.1 strings result.valid = true; fn.call(this, gvlid, moduleName, result); } @@ -102,19 +201,15 @@ export function userSyncHook(fn, ...args) { if (consentData.apiVersion === 2) { const gvlid = getGvlid(); const curBidder = config.getCurrentBidder(); - if (gvlid) { - const purpose1Rule = find(enforcementRules, hasPurpose1); - let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); - if (isAllowed) { - fn.call(this, ...args); - } else { - utils.logWarn(`User sync not allowed for ${curBidder}`); - } + let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); + if (isAllowed) { + fn.call(this, ...args); } else { utils.logWarn(`User sync not allowed for ${curBidder}`); + storageBlocked.push(curBidder); } } else { - utils.logInfo('Enforcing TCF2 only'); + // The module doesn't enforce TCF1.1 strings fn.call(this, ...args); } } else { @@ -132,24 +227,20 @@ export function userIdHook(fn, submodules, consentData) { if (consentData && consentData.gdprApplies) { if (consentData.apiVersion === 2) { let userIdModules = submodules.map((submodule) => { - const gvlid = submodule.submodule.gvlid; + const gvlid = getGvlidForUserIdModule(submodule.submodule); const moduleName = submodule.submodule.name; - if (gvlid) { - const purpose1Rule = find(enforcementRules, hasPurpose1); - let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); - if (isAllowed) { - return submodule; - } else { - utils.logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); - } + let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); + if (isAllowed) { + return submodule; } else { utils.logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); + storageBlocked.push(moduleName); } return undefined; }).filter(module => module) - fn.call(this, userIdModules, {...consentData, hasValidated: true}); + fn.call(this, userIdModules, { ...consentData, hasValidated: true }); } else { - utils.logInfo('Enforcing TCF2 only'); + // The module doesn't enforce TCF1.1 strings fn.call(this, submodules, consentData); } } else { @@ -157,28 +248,137 @@ export function userIdHook(fn, submodules, consentData) { } } -const hasPurpose1 = (rule) => { return rule.purpose === purpose1 } +/** + * Checks if bidders are allowed in the auction. + * Enforces "purpose 2 (Basic Ads)" of TCF v2.0 spec + * @param {Function} fn - Function reference to the original function. + * @param {Array} adUnits + */ +export function makeBidRequestsHook(fn, adUnits, ...args) { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + adUnits.forEach(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => { + const currBidder = bid.bidder; + const gvlId = getGvlid(currBidder); + if (includes(biddersBlocked, currBidder)) return false; + const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); + if (!isAllowed) { + utils.logWarn(`TCF2 blocked auction for ${currBidder}`); + biddersBlocked.push(currBidder); + } + return isAllowed; + }); + }); + fn.call(this, adUnits, ...args); + } else { + // The module doesn't enforce TCF1.1 strings + fn.call(this, adUnits, ...args); + } + } else { + fn.call(this, adUnits, ...args); + } +} + +/** + * Checks if Analytics adapters are allowed to send data to their servers for furhter processing. + * Enforces "purpose 7 (Measurement)" of TCF v2.0 spec + * @param {Function} fn - Function reference to the original function. + * @param {Array} config - Configuration object passed to pbjs.enableAnalytics() + */ +export function enableAnalyticsHook(fn, config) { + const consentData = gdprDataHandler.getConsentData(); + if (consentData && consentData.gdprApplies) { + if (consentData.apiVersion === 2) { + if (!utils.isArray(config)) { + config = [config] + } + config = config.filter(conf => { + const analyticsAdapterCode = conf.provider; + const gvlid = getGvlidForAnalyticsAdapter(analyticsAdapterCode); + const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); + if (!isAllowed) { + analyticsBlocked.push(analyticsAdapterCode); + utils.logWarn(`TCF2 blocked analytics adapter ${conf.provider}`); + } + return isAllowed; + }); + fn.call(this, config); + } else { + // This module doesn't enforce TCF1.1 strings + fn.call(this, config); + } + } else { + fn.call(this, config); + } +} + +/** + * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event. + */ +function emitTCF2FinalResults() { + // remove null and duplicate values + const formatArray = function (arr) { + return arr.filter((i, k) => i !== null && arr.indexOf(i) === k); + } + const tcf2FinalResults = { + storageBlocked: formatArray(storageBlocked), + biddersBlocked: formatArray(biddersBlocked), + analyticsBlocked: formatArray(analyticsBlocked) + }; + + events.emit(EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); +} + +events.on(EVENTS.AUCTION_END, emitTCF2FinalResults); + +/* + Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find(). +*/ +const hasPurpose1 = (rule) => { return rule.purpose === TCF2.purpose1.name } +const hasPurpose2 = (rule) => { return rule.purpose === TCF2.purpose2.name } +const hasPurpose7 = (rule) => { return rule.purpose === TCF2.purpose7.name } /** - * A configuration function that initializes some module variables, as well as add hooks - * @param {Object} config GDPR enforcement config object + * A configuration function that initializes some module variables, as well as adds hooks + * @param {Object} config - GDPR enforcement config object */ export function setEnforcementConfig(config) { const rules = utils.deepAccess(config, 'gdpr.rules'); if (!rules) { - utils.logWarn('GDPR enforcement rules not defined, exiting enforcement module'); - return; + utils.logWarn('TCF2: enforcing P1 and P2'); + enforcementRules = DEFAULT_RULES; + } else { + enforcementRules = rules; } - enforcementRules = rules; - const hasDefinedPurpose1 = find(enforcementRules, hasPurpose1); - if (hasDefinedPurpose1 && !addedDeviceAccessHook) { + purpose1Rule = find(enforcementRules, hasPurpose1); + purpose2Rule = find(enforcementRules, hasPurpose2); + purpose7Rule = find(enforcementRules, hasPurpose7); + + if (!purpose1Rule) { + purpose1Rule = DEFAULT_RULES[0]; + } + + if (!purpose2Rule) { + purpose2Rule = DEFAULT_RULES[1]; + } + + if (purpose1Rule && !addedDeviceAccessHook) { addedDeviceAccessHook = true; validateStorageEnforcement.before(deviceAccessHook, 49); registerSyncInner.before(userSyncHook, 48); // Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build getHook('validateGdprEnforcement').before(userIdHook, 47); } + if (purpose2Rule) { + getHook('makeBidRequests').before(makeBidRequestsHook); + } + + if (purpose7Rule) { + getHook('enableAnalyticsCb').before(enableAnalyticsHook); + } } config.getConfig('consentManagement', config => setEnforcementConfig(config.consentManagement)); diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js new file mode 100644 index 00000000000..48b72671d6a --- /dev/null +++ b/modules/gptPreAuction.js @@ -0,0 +1,101 @@ +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { getHook } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; + +const MODULE_NAME = 'GPT Pre-Auction'; +export let _currentConfig = {}; +let hooksAdded = false; + +export const appendGptSlots = adUnits => { + const { customGptSlotMatching } = _currentConfig; + + if (!utils.isGptPubadsDefined()) { + return; + } + + const adUnitMap = adUnits.reduce((acc, adUnit) => { + acc[adUnit.code] = adUnit; + return acc; + }, {}); + + window.googletag.pubads().getSlots().forEach(slot => { + const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching + ? customGptSlotMatching(slot) + : utils.isAdUnitCodeMatchingSlot(slot)); + + if (matchingAdUnitCode) { + const adUnit = adUnitMap[matchingAdUnitCode]; + adUnit.fpd = adUnit.fpd || {}; + adUnit.fpd.context = adUnit.fpd.context || {}; + + const context = adUnit.fpd.context; + context.adServer = context.adServer || {}; + context.adServer.name = 'gam'; + context.adServer.adSlot = slot.getAdUnitPath(); + } + }); +}; + +export const appendPbAdSlot = adUnit => { + adUnit.fpd = adUnit.fpd || {}; + adUnit.fpd.context = adUnit.fpd.context || {}; + const context = adUnit.fpd.context; + const { customPbAdSlot } = _currentConfig; + + if (customPbAdSlot) { + context.pbAdSlot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adServer.adSlot')); + return; + } + + // use context.pbAdSlot if set + if (context.pbAdSlot) { + return; + } + // use data attribute 'data-adslotid' if set + try { + const adUnitCodeDiv = document.getElementById(adUnit.code); + if (adUnitCodeDiv.dataset.adslotid) { + context.pbAdSlot = adUnitCodeDiv.dataset.adslotid; + return; + } + } catch (e) {} + // banner adUnit, use GPT adunit if defined + if (context.adServer) { + context.pbAdSlot = context.adServer.adSlot; + return; + } + context.pbAdSlot = adUnit.code; +}; + +export const makeBidRequestsHook = (fn, adUnits, ...args) => { + appendGptSlots(adUnits); + adUnits.forEach(adUnit => { + appendPbAdSlot(adUnit); + }); + return fn.call(this, adUnits, ...args); +}; + +const handleSetGptConfig = moduleConfig => { + _currentConfig = utils.pick(moduleConfig, [ + 'enabled', enabled => enabled !== false, + 'customGptSlotMatching', customGptSlotMatching => + typeof customGptSlotMatching === 'function' && customGptSlotMatching, + 'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot, + ]); + + if (_currentConfig.enabled) { + if (!hooksAdded) { + getHook('makeBidRequests').before(makeBidRequestsHook); + hooksAdded = true; + } + } else { + utils.logInfo(`${MODULE_NAME}: Turning off module`); + _currentConfig = {}; + getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); + hooksAdded = false; + } +}; + +config.getConfig('gptPreAuction', config => handleSetGptConfig(config.gptPreAuction)); +handleSetGptConfig({}); diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index d18effa349b..b4b741ac783 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -6,6 +6,7 @@ import {config} from '../src/config.js'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hb'; +const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -40,117 +41,22 @@ export const spec = { * * @param {BidRequest[]} validBidRequests - an array of bids * @param {bidderRequest} bidderRequest bidder request object - * @return ServerRequest Info describing the request to the server. + * @return {ServerRequest[]} Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; - const bids = validBidRequests || []; - let pageKeywords = null; - let reqId; - - bids.forEach(bid => { - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode, mediaTypes} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords); - } - - const addedSizes = {}; - sizesId.forEach((sizeId) => { - addedSizes[sizeId] = true; - }); - const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes')); - const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize')); - bannerSizesId.concat(videoSizesId).forEach((sizeId) => { - if (!addedSizes[sizeId]) { - addedSizes[sizeId] = true; - sizesId.push(sizeId); - } - }); - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); - } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); + const oldFormatBids = []; + const newFormatBids = []; + validBidRequests.forEach((bid) => { + bid.params.useNewFormat ? newFormatBids.push(bid) : oldFormatBids.push(bid); }); - - const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null - }); - - if (configKeywords.length) { - pageKeywords = (pageKeywords || []).concat(configKeywords); - } - - if (pageKeywords && pageKeywords.length > 0) { - pageKeywords.forEach(deleteValues); + const requests = []; + if (newFormatBids.length) { + requests.push(buildNewRequest(newFormatBids, bidderRequest)); } - - const payload = { - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); + if (oldFormatBids.length) { + requests.push(buildOldRequest(oldFormatBids, bidderRequest)); } - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap, - }; + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -162,7 +68,6 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; - const bidsMap = bidRequest.bidsMap; let errorMessage; @@ -173,7 +78,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses); }); } if (errorMessage) utils.logError(errorMessage); @@ -224,68 +129,76 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, bidResponses) { +function _addBidResponse(serverBid, bidRequest, bidResponses) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - const slot = awaitingBids[sizeId][0]; - - const bid = slot.bids.shift(); - - const bidResponse = { - requestId: bid.bidId, // bid.bidderRequestId, - bidderCode: spec.code, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, - currency: 'USD', - netRevenue: false, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid - }; - - if (serverBid.content_type === 'video') { - bidResponse.vastXml = serverBid.adm; - bidResponse.mediaType = VIDEO; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }); - } - } else { - bidResponse.ad = serverBid.adm; - bidResponse.mediaType = BANNER; + let bid = null; + let slot = null; + const bidsMap = bidRequest.bidsMap; + if (bidRequest.newFormat) { + bid = bidsMap[serverBid.impid]; + } else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + const sizeId = `${serverBid.w}x${serverBid.h}`; + if (awaitingBids[sizeId]) { + slot = awaitingBids[sizeId][0]; + bid = slot.bids.shift(); } - bidResponses.push(bidResponse); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } - if (!slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } + if (bid) { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + bidderCode: spec.code, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: false, + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid + }; + + if (serverBid.content_type === 'video') { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL }); } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; + } + bidResponses.push(bidResponse); + + if (slot && !slot.bids.length) { + slot.parents.forEach(({parent, key, uid}) => { + const index = parent[key].indexOf(slot); + if (index > -1) { + parent[key].splice(index, 1); + } + if (!parent[key].length) { + delete parent[key]; + if (!utils.getKeys(parent).length) { + delete bidsMap[uid]; + } + } + }); } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; } } if (errorMessage) { @@ -293,6 +206,326 @@ function _addBidResponse(serverBid, bidsMap, bidResponses) { } } +function buildOldRequest(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const slotsMapByUid = {}; + const sizeMap = {}; + const bids = validBidRequests || []; + let pageKeywords = null; + let reqId; + + bids.forEach(bid => { + reqId = bid.bidderRequestId; + const {params: {uid}, adUnitCode, mediaTypes} = bid; + auids.push(uid); + const sizesId = utils.parseSizesInput(bid.sizes); + + if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { + pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords); + } + + const addedSizes = {}; + sizesId.forEach((sizeId) => { + addedSizes[sizeId] = true; + }); + const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes')); + const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize')); + bannerSizesId.concat(videoSizesId).forEach((sizeId) => { + if (!addedSizes[sizeId]) { + addedSizes[sizeId] = true; + sizesId.push(sizeId); + } + }); + + if (!slotsMapByUid[uid]) { + slotsMapByUid[uid] = {}; + } + const slotsMap = slotsMapByUid[uid]; + if (!slotsMap[adUnitCode]) { + slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; + } else { + slotsMap[adUnitCode].bids.push(bid); + } + const slot = slotsMap[adUnitCode]; + + sizesId.forEach((sizeId) => { + sizeMap[sizeId] = true; + if (!bidsMap[uid]) { + bidsMap[uid] = {}; + } + + if (!bidsMap[uid][sizeId]) { + bidsMap[uid][sizeId] = [slot]; + } else { + bidsMap[uid][sizeId].push(slot); + } + slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); + }); + }); + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + const payload = { + auids: auids.join(','), + sizes: utils.getKeys(sizeMap).join(','), + r: reqId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (pageKeywords) { + payload.keywords = JSON.stringify(pageKeywords); + } + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap: bidsMap + } +} + +function buildNewRequest(validBidRequests, bidderRequest) { + let pageKeywords = null; + let jwpseg = null; + let content = null; + let schain = null; + let userId = null; + let user = null; + let userExt = null; + let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest; + + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; + + validBidRequests.forEach((bid) => { + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userId) { + userId = bid.userId; + } + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, realTimeData} = bid; + bidsMap[bidId] = bid; + if (!pageKeywords && !utils.isEmpty(keywords)) { + pageKeywords = utils.transformBidderParamKeywords(keywords); + } + if (realTimeData && realTimeData.jwTargeting) { + if (!jwpseg && realTimeData.jwTargeting.segments) { + jwpseg = realTimeData.segments; + } + if (!content && realTimeData.content) { + content = realTimeData.content; + } + } + let impObj = { + id: bidId, + tagid: uid.toString(), + ext: { + divid: adUnitCode + } + }; + + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; + } + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; + } + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); + } + }); + + const source = { + tid: auctionId, + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } + }; + + if (schain) { + source.ext.schain = schain; + } + + const tmax = config.getConfig('bidderTimeout') || timeout; + + let request = { + id: bidderRequestId, + site: { + page: referer + }, + tmax, + source, + imp + }; + + if (content) { + request.site.content = content; + } + + if (jwpseg && jwpseg.length) { + user = { + data: [{ + name: 'iow_labs_pub_data', + segment: jwpseg.map((seg) => { + return {name: 'jwpseg', value: seg}; + }) + }] + }; + } + + if (gdprConsent && gdprConsent.consentString) { + userExt = {consent: gdprConsent.consentString}; + } + + if (userId) { + userExt = userExt || {}; + if (userId.tdid) { + userExt.unifiedid = userId.tdid; + } + if (userId.id5id) { + userExt.id5id = userId.id5id; + } + if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) { + userExt.digitrustid = userId.digitrustid.data.id; + } + if (userId.lipb && userId.lipb.lipbid) { + userExt.liveintentid = userId.lipb.lipbid; + } + } + + if (userExt && Object.keys(userExt).length) { + user = user || {}; + user.ext = userExt; + } + + if (user) { + request.user = user; + } + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + if (pageKeywords) { + request.ext = { + keywords: pageKeywords + }; + } + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + } + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; + } + request.regs.ext.us_privacy = uspConsent; + } + + return { + method: 'POST', + url: NEW_ENDPOINT_URL, + data: JSON.stringify(request), + newFormat: true, + bidsMap + }; +} + +function createVideoRequest(bid, mediaType) { + const {playerSize, mimes, durationRangeSec} = mediaType; + const size = (playerSize || bid.sizes || [])[0]; + if (!size) return; + + let result = utils.parseGPTSingleSizeArrayToRtbSize(size); + + if (mimes) { + result.mimes = mimes; + } + + if (durationRangeSec && durationRangeSec.length === 2) { + result.minduration = durationRangeSec[0]; + result.maxduration = durationRangeSec[1]; + } + + return result; +} + +function createBannerRequest(bid, mediaType) { + const sizes = mediaType.sizes || bid.sizes; + if (!sizes || !sizes.length) return; + + let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size)); + let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]); + + if (format.length) { + result.format = format + } + return result; +} + function outstreamRender (bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 54d845be7ad..ebeb46e3c44 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -198,6 +198,34 @@ function _getVidParams (attributes) { }; } +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Number} bidfloor + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bidfloor, bid) { + const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; + let floor = bidfloor || 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + /** * Make a server request from the list of BidRequests. * @@ -219,15 +247,24 @@ function buildRequests (validBidRequests, bidderRequest) { transactionId, userId = {} } = bidRequest; - const bannerSizes = mediaTypes.banner && mediaTypes.banner.sizes; + const bidFloor = _getFloor(mediaTypes, params.bidfloor, bidRequest); + let sizes = [1, 1]; let data = {}; + if (mediaTypes.banner) { + sizes = mediaTypes.banner.sizes; + } else if (mediaTypes.video) { + sizes = mediaTypes.video.playerSize; + } + if (pageViewId) { data.pv = pageViewId; } - if (params.bidfloor) { - data.fp = params.bidfloor; + + if (bidFloor) { + data.fp = bidFloor; } + if (params.inScreenPubID) { data.pubId = params.inScreenPubID; data.pi = 2; @@ -278,7 +315,7 @@ function buildRequests (validBidRequests, bidderRequest) { tId: transactionId, pi: data.pi, selector: params.selector, - sizes: bannerSizes, + sizes, url: BID_ENDPOINT, method: 'GET', data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId), _getTradeDeskIDParam(userId)) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 62d4b015aa7..77d4220e59a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -6,7 +6,7 @@ import isInteger from 'core-js-pure/features/number/is-integer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ix'; -const SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; +const SECURE_BID_URL = 'https://htlb.casalemedia.com/cygnus'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BANNER_ENDPOINT_VERSION = 7.2; const VIDEO_ENDPOINT_VERSION = 8.1; @@ -128,6 +128,9 @@ function parseBid(rawBid, currency, bidRequest) { bid.meta.networkId = utils.deepAccess(rawBid, 'ext.dspid'); bid.meta.brandId = utils.deepAccess(rawBid, 'ext.advbrandid'); bid.meta.brandName = utils.deepAccess(rawBid, 'ext.advbrand'); + if (rawBid.adomain && rawBid.adomain.length > 0) { + bid.meta.advertiserDomains = rawBid.adomain; + } return bid; } diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js index fc0c44f6d82..7e5c06b1b48 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -1,30 +1,113 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; const BIDDER_CODE = 'mediaforce'; const ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid'; +const TEST_ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid?debug_key=abcdefghijklmnop'; +const NATIVE_ID_MAP = {}; +const NATIVE_PARAMS = { + title: { + id: 1, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + cta: { + id: 6, + type: 12, + name: 'data' + }, + body2: { + id: 7, + name: 'data', + type: 10 + }, + rating: { + id: 8, + name: 'data', + type: 3 + }, + likes: { + id: 9, + name: 'data', + type: 4 + }, + downloads: { + id: 10, + name: 'data', + type: 5 + }, + displayUrl: { + id: 11, + name: 'data', + type: 11 + }, + price: { + id: 12, + name: 'data', + type: 6 + }, + salePrice: { + id: 13, + name: 'data', + type: 7 + }, + address: { + id: 14, + name: 'data', + type: 9 + }, + phone: { + id: 15, + name: 'data', + type: 8 + } +}; + +Object.keys(NATIVE_PARAMS).forEach((key) => { + NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key; +}); export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { return !!((typeof bid.params === 'object') && bid.params.placement_id && bid.params.publisher_id); }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {bidderRequest} bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { if (validBidRequests.length === 0) { return; @@ -32,11 +115,12 @@ export const spec = { const referer = bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.referer) : ''; const dnt = utils.getDNT() ? 1 : 0; - let imp = []; - let requests = [] + let requests = []; validBidRequests.forEach(bid => { let tagid = bid.params.placement_id; let bidfloor = bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : 0; + let imp = []; + let validImp = false; let impObj = { id: bid.bidId, tagid: tagid, @@ -47,11 +131,16 @@ export const spec = { switch (mediaTypes) { case BANNER: impObj.banner = createBannerRequest(bid); - imp.push(impObj); + validImp = true; + break; + case NATIVE: + impObj.native = createNativeRequest(bid); + validImp = true; break; default: return; } } + validImp && imp.push(impObj); let request = { id: bid.transactionId, @@ -73,7 +162,7 @@ export const spec = { }; requests.push({ method: 'POST', - url: ENDPOINT_URL, + url: bid.params.is_test ? TEST_ENDPOINT_URL : ENDPOINT_URL, data: JSON.stringify(request) }); }); @@ -81,11 +170,12 @@ export const spec = { }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { if (!serverResponse || !serverResponse.body) { return []; @@ -99,15 +189,35 @@ export const spec = { const bid = { requestId: serverBid.impid, cpm: parseFloat(serverBid.price), - width: serverBid.w, - height: serverBid.h, creativeId: serverBid.adid, currency: cur, netRevenue: true, ttl: serverBid.ttl || 300, - ad: serverBid.adm, burl: serverBid.burl, }; + if (serverBid.dealid) { + bid.dealId = serverBid.dealid; + } + let jsonAdm; + let adm = serverBid.adm; + let ext = serverBid.ext; + try { + jsonAdm = JSON.parse(adm); + } catch (err) {} + if (jsonAdm && jsonAdm.native) { + ext = ext || {}; + ext.native = jsonAdm.native; + adm = null; + } + if (adm) { + bid.width = serverBid.w; + bid.height = serverBid.h; + bid.ad = adm; + bid.mediaType = BANNER; + } else if (ext && ext.native) { + bid.native = parseNative(ext.native); + bid.mediaType = NATIVE; + } bidResponses.push(bid); }) @@ -117,9 +227,9 @@ export const spec = { }, /** - * Register bidder specific code, which will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction - */ + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ onBidWon: function(bid) { const cpm = utils.deepAccess(bid, 'adserverTargeting.hb_pb') || ''; if (utils.isStr(bid.burl) && bid.burl !== '') { @@ -127,7 +237,8 @@ export const spec = { utils.triggerPixel(bid.burl); } }, -} +}; + registerBidder(spec); function getLanguage() { @@ -149,3 +260,73 @@ function createBannerRequest(bid) { } return r } + +function parseNative(native) { + const {assets, link, imptrackers, jstracker} = native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || [], + impressionTrackers: imptrackers || [], + javascriptTrackers: jstracker ? [jstracker] : [] + }; + + (assets || []).forEach((asset) => { + const {id, img, data, title} = asset; + const key = NATIVE_ID_MAP[id]; + if (key) { + if (!utils.isEmpty(title)) { + result.title = title.text + } else if (!utils.isEmpty(img)) { + result[key] = { + url: img.url, + height: img.h, + width: img.w + } + } else if (!utils.isEmpty(data)) { + result[key] = data.value; + } + } + }); + + return result; +} + +function createNativeRequest(bid) { + const assets = []; + if (bid.nativeParams) { + Object.keys(bid.nativeParams).forEach((key) => { + if (NATIVE_PARAMS[key]) { + const {name, type, id} = NATIVE_PARAMS[key]; + const assetObj = type ? {type} : {}; + let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key]; + if (len) { + assetObj.len = len; + } + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + let wmin = aRatios.min_width || 0; + let hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + assetObj.wmin = wmin; + assetObj.hmin = hmin; + } + if (sizes && sizes.length) { + sizes = [].concat(...sizes); + assetObj.w = sizes[0]; + assetObj.h = sizes[1]; + } + const asset = {required: required ? 1 : 0, id}; + asset[name] = assetObj; + assets.push(asset); + } + }); + } + return { + ver: '1.2', + request: { + assets: assets, + context: 1, + plcmttype: 1, + ver: '1.2' + } + } +} diff --git a/modules/mediaforceBidAdapter.md b/modules/mediaforceBidAdapter.md index e16d4178b3f..f8e6903516f 100644 --- a/modules/mediaforceBidAdapter.md +++ b/modules/mediaforceBidAdapter.md @@ -33,3 +33,36 @@ Module that connects to mediaforce's demand sources } ]; ``` + +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [420, 315], + }, + sponsoredBy: { + required: false + } + } + }, + bids: [ + { + bidder: "mediaforce", + params: { + placement_id: 'pl12345', // required + publisher_id: 'pub111', // required + is_test: true + } + } + ] + } + ]; +``` diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 849954fa072..62958cfbfd2 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -4,7 +4,7 @@ import CONSTANTS from '../src/constants.json'; import * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; -import { getPriceGranularity, AUCTION_IN_PROGRESS, AUCTION_COMPLETED } from '../src/auction.js' +import { AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity } from '../src/auction.js' const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -29,6 +29,7 @@ const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const BID_SUCCESS = 1; const BID_NOBID = 2; const BID_TIMEOUT = 3; +const BID_FLOOR_REJECTED = 12; const DUMMY_BIDDER = '-2'; const CONFIG_PENDING = 0; @@ -151,7 +152,7 @@ class PageDetail { this.canonical_url = canonicalUrl; this.og_url = ogUrl; this.twitter_url = twitterUrl; - this.screen = this._getWindowSize() + this.screen = this._getWindowSize(); } _getTopWindowReferrer() { @@ -201,9 +202,9 @@ class PageDetail { } class AdSlot { - constructor(mediaTypes, bannerSizes, tmax, supplyAdCode, adext) { + constructor(mediaTypes, allMediaTypeSizes, tmax, supplyAdCode, adext, context, adSize) { this.mediaTypes = mediaTypes; - this.bannerSizes = bannerSizes; + this.allMediaTypeSizes = allMediaTypeSizes; this.tmax = tmax; this.supplyAdCode = supplyAdCode; this.adext = adext; @@ -213,6 +214,8 @@ class AdSlot { // shouldBeLogged is assigned when requested, // since we are waiting for logging percent response this.shouldBeLogged = undefined; + this.context = context; + this.adSize = adSize; // old ad unit sizes } getShouldBeLogged() { @@ -226,10 +229,12 @@ class AdSlot { return Object.assign({ supcrid: this.supplyAdCode, mediaTypes: this.mediaTypes && this.mediaTypes.join('|'), - szs: this.bannerSizes.join('|'), + szs: this.allMediaTypeSizes.map(sz => sz.join('x')).join('|'), tmax: this.tmax, targ: JSON.stringify(this.targeting), - ismn: this.medianetPresent + ismn: this.medianetPresent, + vplcmtt: this.context, + sz2: this.adSize.map(sz => sz.join('x')).join('|'), }, this.adext && {'adext': JSON.stringify(this.adext)}, ); @@ -261,6 +266,8 @@ class Bid { this.crid = undefined; this.pubcrid = undefined; this.mpvid = undefined; + this.floorPrice = undefined; + this.floorRule = undefined; } get size() { @@ -290,6 +297,8 @@ class Bid { crid: this.crid, pubcrid: this.pubcrid, mpvid: this.mpvid, + bidflr: this.floorPrice, + flrrule: this.floorRule, ext: JSON.stringify(this.ext) } } @@ -306,6 +315,7 @@ class Auction { this.setTargetingTime = undefined; this.auctionEndTime = undefined; this.bidWonTime = undefined; + this.floorData = {}; } hasEnded() { @@ -319,13 +329,22 @@ class Auction { tts: this.setTargetingTime - this.auctionInitTime, wts: this.bidWonTime - this.auctionInitTime, aucstatus: this.status, - acid: this.acid + acid: this.acid, + flrdata: this._mergeFieldsToLog({ + ln: this.floorData.location, + skp: this.floorData.skipped, + enfj: utils.deepAccess(this.floorData, 'enforcements.enforceJS'), + enfd: utils.deepAccess(this.floorData, 'enforcements.floorDeals'), + sr: this.floorData.skipRate, + fs: this.floorData.fetchStatus + }), + flrver: this.floorData.modelVersion } } - addSlot(supplyAdCode, { mediaTypes, bannerSizes, tmax, adext }) { + addSlot(supplyAdCode, { mediaTypes, allMediaTypeSizes, tmax, adext, context, adSize }) { if (supplyAdCode && this.adSlots[supplyAdCode] === undefined) { - this.adSlots[supplyAdCode] = new AdSlot(mediaTypes, bannerSizes, tmax, supplyAdCode, adext); + this.adSlots[supplyAdCode] = new AdSlot(mediaTypes, allMediaTypeSizes, tmax, supplyAdCode, adext, context, adSize); this.addBid( new Bid('-1', DUMMY_BIDDER, 'client', '-1', supplyAdCode) ); @@ -351,13 +370,27 @@ class Auction { getWinnerAdslotBid(adslot) { return this.getAdslotBids(adslot).filter((bid) => bid.winner); } + + _mergeFieldsToLog(objParams) { + let logParams = []; + let value; + for (const param of Object.keys(objParams)) { + value = objParams[param]; + logParams.push(param + '=' + (value === undefined ? '' : value)); + } + return logParams.join('||'); + } } -function auctionInitHandler({auctionId, timestamp}) { +function auctionInitHandler({auctionId, timestamp, bidderRequests}) { if (auctionId && auctions[auctionId] === undefined) { auctions[auctionId] = new Auction(auctionId); auctions[auctionId].auctionInitTime = timestamp; } + const floorData = utils.deepAccess(bidderRequests, '0.bids.0.floorData'); + if (floorData) { + auctions[auctionId].floorData = {...floorData}; + } } function bidRequestedHandler({ auctionId, auctionStart, bids, start, timeout, uspConsent, gdpr }) { @@ -375,13 +408,16 @@ function bidRequestedHandler({ auctionId, auctionStart, bids, start, timeout, us const { adUnitCode, bidder, mediaTypes, sizes, bidId, src } = bid; if (!auctions[auctionId].adSlots[adUnitCode]) { auctions[auctionId].auctionStartTime = auctionStart; + const sizeObject = _getSizes(mediaTypes, sizes); auctions[auctionId].addSlot( adUnitCode, Object.assign({}, (mediaTypes instanceof Object) && { mediaTypes: Object.keys(mediaTypes) }, - { bannerSizes: utils.deepAccess(mediaTypes, 'banner.sizes') || sizes || [] }, + { allMediaTypeSizes: [].concat(sizeObject.banner, sizeObject.native, sizeObject.video) }, { adext: utils.deepAccess(mediaTypes, 'banner.ext') || '' }, - { tmax: timeout } + { tmax: timeout }, + { context: utils.deepAccess(mediaTypes, 'video.context') || '' }, + { adSize: sizeObject.banner } ) ); } @@ -395,6 +431,17 @@ function bidRequestedHandler({ auctionId, auctionStart, bids, start, timeout, us }); } +function _getSizes(mediaTypes, sizes) { + const banner = utils.deepAccess(mediaTypes, 'banner.sizes') || sizes || []; + const native = utils.deepAccess(mediaTypes, 'native') ? [[1, 1]] : []; + const playerSize = utils.deepAccess(mediaTypes, 'video.playerSize') || []; + let video = []; + if (playerSize.length === 2) { + video = [playerSize] + } + return { banner, native, video } +} + function bidResponseHandler(bid) { const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId } = bid; const {originalCpm, bidderCode, creativeId, adId, currency} = bid; @@ -411,6 +458,8 @@ function bidResponseHandler(bid) { { cpm, width, height, mediaType, timeToRespond, dealId, creativeId }, { adId, currency } ); + bidObj.floorPrice = utils.deepAccess(bid, 'floorData.floorValue'); + bidObj.floorRule = utils.deepAccess(bid, 'floorData.floorRule'); bidObj.originalCpm = originalCpm || cpm; let dfpbd = utils.deepAccess(bid, 'adserverTargeting.hb_pb'); if (!dfpbd) { @@ -419,7 +468,11 @@ function bidResponseHandler(bid) { dfpbd = bid[priceGranularityKey] || cpm; } bidObj.dfpbd = dfpbd; - bidObj.status = BID_SUCCESS; + if (bid.status === CONSTANTS.BID_STATUS.BID_REJECTED) { + bidObj.status = BID_FLOOR_REJECTED; + } else { + bidObj.status = BID_SUCCESS; + } if (bidderCode === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { Object.assign( diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 5cc18acb424..9ff0192eab4 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'medianet'; @@ -21,7 +21,7 @@ let refererInfo = getRefererInfo(); let mnData = {}; mnData.urlData = { - domain: utils.parseUrl(refererInfo.referer).host, + domain: utils.parseUrl(refererInfo.referer).hostname, page: refererInfo.referer, isTop: refererInfo.reachedTop } @@ -160,7 +160,15 @@ function slotParams(bidRequest) { }, all: bidRequest.params }; - let bannerSizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes || []; + let bannerSizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []; + + const videoInMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video') || {}; + const videoInParams = utils.deepAccess(bidRequest, 'params.video') || {}; + const videoCombinedObj = Object.assign({}, videoInParams, videoInMediaType); + + if (!utils.isEmpty(videoCombinedObj)) { + params.video = videoCombinedObj; + } if (bannerSizes.length > 0) { params.banner = transformSizes(bannerSizes); @@ -305,7 +313,7 @@ export const spec = { code: BIDDER_CODE, gvlid: 142, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** * Determines whether or not the given bid request is valid. diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md index 8259c32c9d3..b465d678fc9 100644 --- a/modules/medianetBidAdapter.md +++ b/modules/medianetBidAdapter.md @@ -14,19 +14,24 @@ This adapter currently only supports Banner Ads. # Sample Ad Unit: For Publishers ```javascript var adUnits = [{ - code: 'media.net-hb-ad-123456-1', - sizes: [ - [300, 250], - [300, 600], - ], - bids: [{ - bidder: 'medianet', - params: { - cid: '', - bidfloor: '', - crid: '' - } - }] + code: 'media.net-hb-ad-123456-1', + mediaTypes: { + banner: { + sizes: [ + [728, 90], + [300, 600], + [300, 250] + ], + } + }, + bids: [{ + bidder: 'medianet', + params: { + cid: '', + bidfloor: '', + crid: '' + } + }] }]; ``` @@ -35,69 +40,146 @@ var adUnits = [{ ```html ``` +# Ad Unit and Setup: For Testing (Video Instream) -# Ad Unit and Setup: For Testing (Native) +```html + + + +``` +# Ad Unit and Setup: For Testing (Video Outstream) ```html +``` + +# Ad Unit and Setup: For Testing (Native) + +```html + + + + diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index cb52c288caf..288526d3cc5 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -136,7 +136,7 @@ export const spec = { // fires a pixel to confirm a winning bid let params = []; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; - let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond'] + let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'auctionId', 'requestId'] if (bid.hasOwnProperty('mediasquare')) { if (bid['mediasquare'].hasOwnProperty('bidder')) { params.push('bidder=' + bid['mediasquare']['bidder']); } if (bid['mediasquare'].hasOwnProperty('code')) { params.push('code=' + bid['mediasquare']['code']); } diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 6280dd12268..fd66c8ce69f 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -4,7 +4,10 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; import find from 'core-js-pure/features/array/find.js'; -const { registerBidder } = require('../src/adapters/bidderFactory.js'); +import { getStorageManager } from '../src/storageManager.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const storage = getStorageManager(); const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; @@ -62,14 +65,15 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidderRequest && bidderRequest.userId) { payload.userId = bidderRequest.userId; } - if (window.localStorage) { - payload.onetagSid = window.localStorage.getItem('onetag_sid'); - } - const payloadString = JSON.stringify(payload); + try { + if (storage.hasLocalStorage()) { + payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); + } + } catch (e) {} return { method: 'POST', url: ENDPOINT, - data: payloadString + data: JSON.stringify(payload) } } @@ -161,17 +165,22 @@ function onetagRenderer({renderer, width, height, vastXml, adUnitCode}) { } function getFrameNesting() { - let frame = window; + let topmostFrame = window; + let parent = window.parent; + let currentFrameNesting = 0; try { - while (frame !== frame.top) { + while (topmostFrame !== topmostFrame.parent) { + parent = topmostFrame.parent; // eslint-disable-next-line no-unused-expressions - frame.location.href; - frame = frame.parent; + parent.location.href; + topmostFrame = topmostFrame.parent; } - } catch (e) {} + } catch (e) { + currentFrameNesting = parent === topmostFrame.top ? 1 : 2; + } return { - topmostFrame: frame, - currentFrameNesting: frame.top === frame ? 1 : 2 + topmostFrame, + currentFrameNesting } } @@ -198,8 +207,11 @@ function getDocumentVisibility(window) { function getPageInfo() { const { topmostFrame, currentFrameNesting } = getFrameNesting(); return { - location: encodeURIComponent(topmostFrame.location.href), - referrer: encodeURIComponent(topmostFrame.document.referrer) || '0', + location: topmostFrame.location.href, + referrer: + topmostFrame.document.referrer !== '' + ? topmostFrame.document.referrer + : null, masked: currentFrameNesting, wWidth: topmostFrame.innerWidth, wHeight: topmostFrame.innerHeight, @@ -216,7 +228,11 @@ function getPageInfo() { docHidden: getDocumentVisibility(topmostFrame), docHeight: topmostFrame.document.body ? topmostFrame.document.body.scrollHeight : null, hLength: history.length, - timing: getTiming() + timing: getTiming(), + version: { + prebid: '$prebid.version$', + adapter: '1.0.0' + } }; } @@ -256,6 +272,7 @@ function setGeneralInfo(bidRequest) { this['auctionId'] = bidRequest.auctionId; this['transactionId'] = bidRequest.transactionId; this['pubId'] = params.pubId; + this['ext'] = params.ext; if (params.pubClick) { this['click'] = params.pubClick; } @@ -326,7 +343,7 @@ function parseSizes(bid) { function getSizes(sizes) { const ret = []; - for (let i = 0, lenght = sizes.length; i < lenght; i++) { + for (let i = 0; i < sizes.length; i++) { const size = sizes[i]; ret.push({width: size[0], height: size[1]}) } diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index d5630c2fad4..f4b6288cd55 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -6,7 +6,9 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openx'; const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '3.0.2'; +const BIDDER_VERSION = '3.0.3'; + +const DEFAULT_CURRENCY = 'USD'; export const USER_ID_CODE_TO_QUERY_ARG = { britepoolid: 'britepoolid', // BritePool ID @@ -16,7 +18,7 @@ export const USER_ID_CODE_TO_QUERY_ARG = { idl_env: 'lre', // LiveRamp IdentityLink lipb: 'lipbid', // LiveIntent ID netId: 'netid', // netID - parrableid: 'parrableid', // Parrable ID + parrableId: 'parrableid', // Parrable ID pubcid: 'pubcid', // PubCommon ID tdid: 'ttduuid', // The Trade Desk Unified ID }; @@ -276,6 +278,9 @@ function appendUserIdsToQueryParams(queryParams, userIds) { case 'lipb': queryParams[key] = userIdObjectOrValue.lipbid; break; + case 'parrableId': + queryParams[key] = userIdObjectOrValue.eid; + break; default: queryParams[key] = userIdObjectOrValue; } @@ -336,8 +341,10 @@ function buildOXBannerRequest(bids, bidderRequest) { let customFloorsForAllBids = []; let hasCustomFloor = false; bids.forEach(function (bid) { - if (bid.params.customFloor) { - customFloorsForAllBids.push((Math.round(bid.params.customFloor * 100) / 100) * 1000); + let floor = getBidFloor(bid, BANNER); + + if (floor) { + customFloorsForAllBids.push(floor); hasCustomFloor = true; } else { customFloorsForAllBids.push(0); @@ -416,6 +423,10 @@ function generateVideoParameters(bid, bidderRequest) { queryParams.vmimes = oxVideoConfig.mimes; } + if (bid.params.test) { + queryParams.vtest = 1; + } + return queryParams; } @@ -449,4 +460,20 @@ function createVideoBidResponses(response, {bid, startTime}) { return bidResponses; } +function getBidFloor(bidRequest, mediaType) { + let floorInfo = {}; + const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: currency, + mediaType: mediaType, + size: '*' + }); + } + let floor = floorInfo.floor || bidRequest.params.customFloor || 0; + + return Math.round(floor * 1000); // normalize to microCpm +} + registerBidder(spec); diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index d14e2bebd72..e01746af487 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -1,18 +1,20 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); +const storageManager = getStorageManager(); export const spec = { code: 'orbidder', - orbidderHost: (() => { - let ret = 'https://orbidder.otto.de'; + hostname: 'https://orbidder.otto.de', + + getHostname() { + let ret = this.hostname; try { - ret = storage.getDataFromLocalStorage('ov_orbidder_host') || ret; + ret = storageManager.getDataFromLocalStorage('ov_orbidder_host') || ret; } catch (e) { } return ret; - })(), + }, isBidRequestValid(bid) { return !!(bid.sizes && bid.bidId && bid.params && @@ -23,6 +25,7 @@ export const spec = { }, buildRequests(validBidRequests, bidderRequest) { + const hostname = this.getHostname(); return validBidRequests.map((bidRequest) => { let referer = ''; if (bidderRequest && bidderRequest.refererInfo) { @@ -30,7 +33,7 @@ export const spec = { } const ret = { - url: `${spec.orbidderHost}/bid`, + url: `${hostname}/bid`, method: 'POST', options: { withCredentials: true }, data: { diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 87f140556d0..451ab654c53 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -539,7 +539,7 @@ export const spec = { */ findAllUserIds(bidRequest) { var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableid', 'idl_env', 'digitrustid', 'criteortus']; + let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'digitrustid', 'criteortus']; if (bidRequest.hasOwnProperty('userId')) { for (let arrayId in searchKeysSingle) { let key = searchKeysSingle[arrayId]; @@ -679,7 +679,7 @@ export const spec = { this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteortus.${BIDDER_CODE}.userid`), 'criteortus', 1); this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.lipb.lipbid`), 'liveintent.com', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableid`), 'parrable.com', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableId.eid`), 'parrable.com', 1); } return eids; }, diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index ec033e62983..75f89fffc14 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -204,7 +204,7 @@ export const parrableIdSubmodule = { */ decode(parrableId) { if (parrableId && utils.isPlainObject(parrableId)) { - return { 'parrableid': parrableId.eid }; + return { parrableId }; } return undefined; }, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index a5dcf1b1d3b..b3d559d956f 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -240,27 +240,6 @@ function doClientSideSyncs(bidders) { }); } -function _getDigiTrustQueryParams(bidRequest = {}) { - function getDigiTrustId(bidRequest) { - const bidRequestDigitrust = utils.deepAccess(bidRequest, 'bids.0.userId.digitrustid.data'); - if (bidRequestDigitrust) { - return bidRequestDigitrust; - } - - const digiTrustUser = config.getConfig('digiTrustId'); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; - } - let digiTrustId = getDigiTrustId(bidRequest); - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return null; - } - return { - id: digiTrustId.id, - keyv: digiTrustId.keyv - }; -} - function _appendSiteAppDevice(request, pageUrl) { if (!request) return; @@ -568,7 +547,16 @@ const OPEN_RTB_PROTOCOL = { */ const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(imp, 'ext.context.data.adslot', pbAdSlot); + utils.deepSetValue(imp, 'ext.context.data.pbadslot', pbAdSlot); + } + + /** + * GAM Ad Unit + * @type {(string|undefined)} + */ + const gamAdUnit = utils.deepAccess(adUnit, 'fpd.context.adServer.adSlot'); + if (typeof gamAdUnit === 'string' && gamAdUnit) { + utils.deepSetValue(imp, 'ext.context.data.adslot', gamAdUnit); } Object.assign(imp, mediaTypes); @@ -627,11 +615,6 @@ const OPEN_RTB_PROTOCOL = { _appendSiteAppDevice(request, firstBidRequest.refererInfo.referer); - const digiTrust = _getDigiTrustQueryParams(firstBidRequest); - if (digiTrust) { - utils.deepSetValue(request, 'user.ext.digitrust', digiTrust); - } - // pass schain object if it is present const schain = utils.deepAccess(bidRequests, '0.bids.0.schain'); if (schain) { @@ -658,6 +641,9 @@ const OPEN_RTB_PROTOCOL = { } utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); utils.deepSetValue(request, 'user.ext.consent', firstBidRequest.gdprConsent.consentString); + if (firstBidRequest.gdprConsent.addtlConsent && typeof firstBidRequest.gdprConsent.addtlConsent === 'string') { + utils.deepSetValue(request, 'user.ext.ConsentedProvidersSettings.consented_providers', firstBidRequest.gdprConsent.addtlConsent); + } } // US Privacy (CCPA) support diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 6d35a0a74cc..eb1f3aed84c 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -286,9 +286,10 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { bid.auctionId = auctionId; bid.floorData = { skipped: floorData.skipped, - modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), - location: utils.deepAccess(floorData, 'data.location'), skipRate: floorData.skipRate, + modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), + location: utils.deepAccess(floorData, 'data.location', 'noData'), + floorProvider: floorData.floorProvider, fetchStatus: _floorsConfig.fetchStatus } }); @@ -513,6 +514,7 @@ export function handleFetchResponse(fetchResponse) { _floorsConfig.data = fetchData; // set skipRate override if necessary _floorsConfig.skipRate = utils.isNumber(fetchData.skipRate) ? fetchData.skipRate : _floorsConfig.skipRate; + _floorsConfig.floorProvider = fetchData.floorProvider || _floorsConfig.floorProvider; } // if any auctions are waiting for fetch to finish, we need to continue them! @@ -568,6 +570,7 @@ export function handleSetFloorsConfig(config) { _floorsConfig = utils.pick(config, [ 'enabled', enabled => enabled !== false, // defaults to true 'auctionDelay', auctionDelay => auctionDelay || 0, + 'floorProvider', floorProvider => utils.deepAccess(config, 'data.floorProvider', floorProvider), 'endpoint', endpoint => endpoint || {}, 'skipRate', () => !isNaN(utils.deepAccess(config, 'data.skipRate')) ? config.data.skipRate : config.skipRate || 0, 'enforcement', enforcement => utils.pick(enforcement || {}, [ diff --git a/modules/priceFloors.md b/modules/priceFloors.md index d09be78c620..36ac07ee972 100644 --- a/modules/priceFloors.md +++ b/modules/priceFloors.md @@ -11,6 +11,7 @@ pbjs.setConfig({ enforceJS: true //defaults to true }, auctionDelay: 150, // in milliseconds defaults to 0 + floorProvider: 'awesomeFloorProviderName', // name of the floor provider (optional) endpoint: { url: 'http://localhost:1500/floor-domains', method: 'GET' // Only get supported for now @@ -40,6 +41,7 @@ pbjs.setConfig({ | enabled | Wether to turn off or on the floors module | | enforcement | object of booleans which control certain features of the module | | auctionDelay | The time to suspend and auction while waiting for a real time price floors fetch to come back | +| floorProvider | A string identifying the floor provider.| | endpoint | An object describing the endpoint to retrieve floor data from. GET only | | data | The data to be used to select appropriate floors. See schema for more detail | | additionalSchemaFields | An object of additional fields to be used in a floor data object. The schema is KEY: function to retrieve the match | @@ -59,4 +61,4 @@ This function can takes in an object with the following optional parameters: If a bid adapter passes in `*` as an attribute, then the `priceFloors` module will attempt to select the best rule based on context. -For example, if an adapter passes in a `*`, but the bidRequest only has a single size and a single mediaType, then the `getFloor` function will attempt to get a rule for that size before matching with the `*` catch-all. Similarily, if mediaType can be inferred on the bidRequest, it will use it. \ No newline at end of file +For example, if an adapter passes in a `*`, but the bidRequest only has a single size and a single mediaType, then the `getFloor` function will attempt to get a rule for that size before matching with the `*` catch-all. Similarily, if mediaType can be inferred on the bidRequest, it will use it. diff --git a/modules/projectLimeLightBidAdapter.js b/modules/projectLimeLightBidAdapter.js index f2ff77f6229..1beba906917 100644 --- a/modules/projectLimeLightBidAdapter.js +++ b/modules/projectLimeLightBidAdapter.js @@ -4,7 +4,6 @@ import {ajax} from '../src/ajax.js'; import * as utils from '../src/utils.js'; const BIDDER_CODE = 'project-limelight'; -const URL = 'https://ads.project-limelight.com/hb'; /** * Determines whether or not the given bid response is valid. @@ -25,19 +24,6 @@ function isBidResponseValid(bid) { return false; } -function extractBidSizes(bid) { - const bidSizes = []; - - bid.sizes.forEach(size => { - bidSizes.push({ - width: size[0], - height: size[1] - }); - }); - - return bidSizes; -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -65,30 +51,10 @@ export const spec = { } catch (e) { utils.logMessage(e); winTop = window; - }; - const placements = []; - const request = { - 'secure': (location.protocol === 'https:'), - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'adUnits': placements - }; - for (let i = 0; i < validBidRequests.length; i++) { - const bid = validBidRequests[i]; - const params = bid.params; - placements.push({ - id: params.adUnitId, - bidId: bid.bidId, - transactionId: bid.transactionId, - sizes: extractBidSizes(bid), - type: params.adUnitType.toUpperCase() - }); } - return { - method: 'POST', - url: URL, - data: request - }; + const placements = utils.groupBy(validBidRequests.map(bidRequest => buildPlacement(bidRequest)), 'host') + return Object.keys(placements) + .map(host => buildRequest(winTop, host, placements[host].map(placement => placement.adUnit))); }, onBidWon: (bid) => { @@ -123,3 +89,34 @@ export const spec = { }; registerBidder(spec); + +function buildRequest(winTop, host, adUnits) { + return { + method: 'POST', + url: `https://${host}/hb`, + data: { + secure: (location.protocol === 'https:'), + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + adUnits: adUnits + } + } +} + +function buildPlacement(bidRequest) { + return { + host: bidRequest.params.host, + adUnit: { + id: bidRequest.params.adUnitId, + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + sizes: bidRequest.sizes.map(size => { + return { + width: size[0], + height: size[1] + } + }), + type: bidRequest.params.adUnitType.toUpperCase() + } + } +} diff --git a/modules/projectLimeLightBidAdapter.md b/modules/projectLimeLightBidAdapter.md index 71621983b89..15aa170cd2e 100644 --- a/modules/projectLimeLightBidAdapter.md +++ b/modules/projectLimeLightBidAdapter.md @@ -18,6 +18,7 @@ Module that connects to Project Limelight SSP demand sources bids: [{ bidder: 'project-limelight', params: { + host: 'ads.project-limelight.com', adUnitId: 0, adUnitType: 'banner' } @@ -34,6 +35,7 @@ var videoAdUnit = [{ bids: [{ bidder: 'project-limelight', params: { + host: 'ads.project-limelight.com', adUnitId: 0, adUnitType: 'video' } diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index 8e2be1207f5..9516934de42 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -7,11 +7,14 @@ import * as utils from '../src/utils.js'; import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; const PUB_COMMON_ID = 'PublisherCommonId'; const MODULE_NAME = 'pubCommonId'; +const storage = getStorageManager(null, 'pubCommonId'); + /** @type {Submodule} */ export const pubCommonIdSubmodule = { /** @@ -90,6 +93,33 @@ export const pubCommonIdSubmodule = { const callback = this.makeCallback(pixelUrl, storedId); return callback ? {callback: callback} : {id: storedId}; } + }, + + /** + * @param {string} domain + * @param {HTMLDocument} document + * @return {(string|undefined)} + */ + domainOverride: function () { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}`; + for (let i = 0, topDomain; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + if (storage.getCookie(cookieName) === '1') { + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; + } + } } }; diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index d42073b3da5..05f18f99a9a 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -16,7 +16,6 @@ import { const BIDDER_VERSION = '1.0.0'; const BASE_URL = 'https://ortb.adpearl.io'; -const AUCTION_URL = BASE_URL + '/prebid/auction'; export const spec = { code: 'pubgenius', @@ -31,7 +30,7 @@ export const spec = { } const sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - return Boolean(sizes && sizes.length) && sizes.every(size => isArrayOfNums(size, 2)); + return !!(sizes && sizes.length) && sizes.every(size => isArrayOfNums(size, 2)); }, buildRequests: function (bidRequests, bidderRequest) { @@ -66,7 +65,7 @@ export const spec = { deepSetValue(data, 'regs.ext.us_privacy', usp); } - const schain = bidderRequest.schain; + const schain = bidRequests[0].schain; if (schain) { deepSetValue(data, 'source.ext.schain', schain); } @@ -85,7 +84,7 @@ export const spec = { return { method: 'POST', - url: AUCTION_URL, + url: `${getBaseUrl()}/prebid/auction`, data, }; }, @@ -128,7 +127,7 @@ export const spec = { const qs = parseQueryStringParameters(params); syncs.push({ type: 'iframe', - url: `${BASE_URL}/usersync/pixels.html?${qs}`, + url: `${getBaseUrl()}/usersync/pixels.html?${qs}`, }); } @@ -136,7 +135,7 @@ export const spec = { }, onTimeout(data) { - ajax(`${BASE_URL}/prebid/events?type=timeout`, null, JSON.stringify(data), { + ajax(`${getBaseUrl()}/prebid/events?type=timeout`, null, JSON.stringify(data), { method: 'POST', }); }, @@ -170,14 +169,26 @@ function buildImp(bid) { } function buildSite(bidderRequest) { - const pageUrl = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + let site = null; + const { refererInfo } = bidderRequest; + + const pageUrl = config.getConfig('pageUrl') || refererInfo.canonicalUrl || refererInfo.referer; if (pageUrl) { - return { - page: pageUrl, - }; + site = site || {}; + site.page = pageUrl; + } + + if (refererInfo.reachedTop) { + try { + const pageRef = window.top.document.referrer; + if (pageRef) { + site = site || {}; + site.ref = pageRef; + } + } catch (e) {} } - return null; + return site; } function interpretBid(bid) { @@ -205,4 +216,9 @@ function numericBoolean(value) { return value ? 1 : 0; } +function getBaseUrl() { + const pubg = config.getConfig('pubgenius'); + return (pubg && pubg.endpoint) || BASE_URL; +} + registerBidder(spec); diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index a050d499640..d21854a57c4 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; @@ -14,6 +15,8 @@ const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; +const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) +const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -104,6 +107,60 @@ const dealChannelValues = { 5: 'PREF', 6: 'PMPG' }; +// BB stands for Blue BillyWig +const BB_RENDERER = { + bootstrapPlayer: function(bid) { + const config = { + code: bid.adUnitCode, + }; + + if (bid.vastXml) config.vastXml = bid.vastXml; + else if (bid.vastUrl) config.vastUrl = bid.vastUrl; + + if (!bid.vastXml && !bid.vastUrl) { + utils.logWarn(`${LOG_WARN_PREFIX}: No vastXml or vastUrl on bid, bailing...`); + return; + } + + const rendererId = BB_RENDERER.getRendererId(PUBLICATION, bid.rendererCode); + + const ele = document.getElementById(bid.adUnitCode); // NB convention + + let renderer; + + for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) { + if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) { + renderer = window.bluebillywig.renderers[rendererIndex]; + break; + } + } + + if (renderer) renderer.bootstrap(config, ele); + else utils.logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`); + }, + newRenderer: function(rendererCode, adUnitCode) { + var rendererUrl = RENDERER_URL.replace('$RENDERER', rendererCode); + const renderer = Renderer.install({ + url: rendererUrl, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(BB_RENDERER.outstreamRender); + } catch (err) { + utils.logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err); + } + + return renderer; + }, + outstreamRender: function(bid) { + bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) }); + }, + getRendererId: function(pub, renderer) { + return `${pub}-${renderer}`; // NB convention! + } +}; let publisherId = 0; let isInvalidNativeRequest = false; @@ -760,6 +817,23 @@ function _handleDealCustomTargetings(payload, dctrArr, validBidRequests) { } } +function _assignRenderer(newBid, request) { + let bidParams, context, adUnitCode; + if (request.bidderRequest && request.bidderRequest.bids) { + for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { + if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) { + bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; + context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode; + } + } + if (context && context === 'outstream' && bidParams && bidParams.outstreamAU && adUnitCode) { + newBid.rendererCode = bidParams.outstreamAU; + newBid.renderer = BB_RENDERER.newRenderer(newBid.rendererCode, adUnitCode); + } + } +}; + export const spec = { code: BIDDER_CODE, gvlid: 76, @@ -782,6 +856,19 @@ export const spec = { utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + utils.logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); + return false; + } + if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU)) { + utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids outstreamAU is required. Rejecting bid: `, bid); + return false; + } + } else { + utils.logError(`${LOG_WARN_PREFIX}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); + return false; + } } return true; } @@ -865,6 +952,11 @@ export const spec = { payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim(); payload.site.domain = _getDomainFromURL(payload.site.page); + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + payload.site.content = config.getConfig('content'); + } + // merge the device from config.getConfig('device') if (typeof config.getConfig('device') === 'object') { payload.device = Object.assign(payload.device, config.getConfig('device')); @@ -910,13 +1002,19 @@ export const spec = { // not copying domain from site as it is a derived value from page payload.app.publisher = payload.site.publisher; payload.app.ext = payload.site.ext || UNDEFINED; + // We will also need to pass content object in app.content if app object is also set into the config; + // BUT do not use content object from config if content object is present in app as app.content + if (typeof payload.app.content !== 'object') { + payload.app.content = payload.site.content || UNDEFINED; + } delete payload.site; } return { method: 'POST', url: ENDPOINT, - data: JSON.stringify(payload) + data: JSON.stringify(payload), + bidderRequest: bidderRequest }; }, @@ -966,6 +1064,7 @@ export const spec = { newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; newBid.vastXml = bid.adm; + _assignRenderer(newBid, request); break; case NATIVE: _parseNativeResponse(bid, newBid); @@ -986,6 +1085,7 @@ export const spec = { newBid.meta.buyerId = bid.ext.advid; } if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.advertiserDomains = bid.adomain; newBid.meta.clickUrl = bid.adomain[0]; } diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index a045bed3e2b..cd9398477f4 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -25,6 +25,7 @@ var adUnits = [ bidder: 'pubmatic', params: { publisherId: '156209', // required + oustreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream'. This value can be get by BlueBillyWig Team. adSlot: 'pubmatic_test2', // optional pmzoneid: 'zone1, zone11', // optional lat: '40.712775', // optional diff --git a/modules/pubperfAnalyticsAdapter.js b/modules/pubperfAnalyticsAdapter.js new file mode 100644 index 00000000000..800ea1cd550 --- /dev/null +++ b/modules/pubperfAnalyticsAdapter.js @@ -0,0 +1,36 @@ +/** + * Analytics Adapter for Pubperf + */ + +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; + +var pubperfAdapter = adapter({ + global: 'pubperf_pbjs', + analyticsType: 'bundle', + handler: 'on' +}); + +pubperfAdapter.originEnableAnalytics = pubperfAdapter.enableAnalytics; + +pubperfAdapter.enableAnalytics = config => { + if (!config || !config.provider || config.provider !== 'pubperf') { + utils.logError('expected config.provider to equal pubperf'); + return; + } + if (!window['pubperf_pbjs']) { + utils.logError( + `Make sure that Pubperf tag from https://www.pubperf.com is included before the Prebid configuration.` + ); + return; + } + pubperfAdapter.originEnableAnalytics(config); +} + +adapterManager.registerAnalyticsAdapter({ + adapter: pubperfAdapter, + code: 'pubperf' +}); + +export default pubperfAdapter; diff --git a/modules/pubperfAnalyticsAdapter.md b/modules/pubperfAnalyticsAdapter.md new file mode 100644 index 00000000000..50ac3691dda --- /dev/null +++ b/modules/pubperfAnalyticsAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: Pubperf Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@transfon.com +``` + +# Description + +Transfon's pubperf analytics adaptor allows you to view detailed auction and prebid information in Meridian. Contact support@transfon.com for more information or to sign up for analytics. + +For more information, please visit https://www.pubperf.com. + + +# Sample pubperf tag to be placed before prebid tag + +``` +(function(i, s, o, g, r, a, m, z) {i['pubperf_pbjs'] = r;i[r] = i[r] || function() {z = Array.prototype.slice.call(arguments);z.unshift(+new Date());(i[r].q = i[r].q || []).push(z)}, i[r].t = 1, i[r].l = 1 * new Date();a = s.createElement(o),m = s.getElementsByTagName(o)[0];a.async = 1;a.src = g;m.parentNode.insertBefore(a, m)})(window, document, 'script', 'https://t.pubperf.com/t/b5a635e307.js', 'pubperf_pbjs'); +``` + +# Test Parameters +``` +{ + provider: 'pubperf' +} +``` diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 7bfa8686728..33fdaa44100 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -421,7 +421,7 @@ function user(bidRequest, bidderRequest) { addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo'); addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'identityLink'); addExternalUserId(ext.eids, bidRequest.userId.id5id, 'id5-sync.com'); - addExternalUserId(ext.eids, bidRequest.userId.parrableid, 'parrable.com'); + addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com'); // liveintent if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { addExternalUserId(ext.eids, bidRequest.userId.lipb.lipbid, 'liveintent.com'); diff --git a/modules/quantumBidAdapter.js b/modules/quantumBidAdapter.js deleted file mode 100644 index f5da6e022f1..00000000000 --- a/modules/quantumBidAdapter.js +++ /dev/null @@ -1,320 +0,0 @@ -import * as utils from '../src/utils.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'quantum'; -const ENDPOINT_URL = 'https://s.sspqns.com/hb'; -export const spec = { - code: BIDDER_CODE, - aliases: ['quantx', 'qtx'], // short code - supportedMediaTypes: [BANNER, NATIVE], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - - isBidRequestValid: function (bid) { - return !!(bid.params && bid.params.placementId); - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (bidRequests, bidderRequest) { - return bidRequests.map(bid => { - const qtxRequest = {}; - let bidId = ''; - - const params = bid.params; - let placementId = params.placementId; - - let devEnpoint = false; - if (params.useDev && params.useDev === '1') { - devEnpoint = 'https://sdev.sspqns.com/hb'; - } - let renderMode = 'native'; - for (let i = 0; i < bid.sizes.length; i++) { - if (bid.sizes[i][0] > 1 && bid.sizes[i][1] > 1) { - renderMode = 'banner'; - break; - } - } - - let mediaType = (bid.mediaType === 'native' || utils.deepAccess(bid, 'mediaTypes.native')) ? 'native' : 'banner'; - - if (mediaType === 'native') { - renderMode = 'native'; - } - - if (!bidId) { - bidId = bid.bidId; - } - qtxRequest.auid = placementId; - - if (bidderRequest && bidderRequest.gdprConsent) { - qtxRequest.quantx_user_consent_string = bidderRequest.gdprConsent.consentString; - qtxRequest.quantx_gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; - }; - - const url = devEnpoint || ENDPOINT_URL; - - return { - method: 'GET', - bidId: bidId, - sizes: bid.sizes, - mediaType: mediaType, - renderMode: renderMode, - url: url, - 'data': qtxRequest, - bidderRequest - }; - }); - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequest) { - const serverBody = serverResponse.body; - const bidResponses = []; - let responseCPM; - let bid = {}; - let id = bidRequest.bidId; - - if (serverBody.price && serverBody.price !== 0) { - responseCPM = parseFloat(serverBody.price); - - bid.creativeId = serverBody.creative_id || '0'; - bid.cpm = responseCPM; - bid.requestId = bidRequest.bidId; - bid.width = 1; - bid.height = 1; - bid.ttl = 200; - bid.netRevenue = true; - bid.currency = 'USD'; - - if (serverBody.native) { - bid.native = serverBody.native; - } - if (serverBody.cobj) { - bid.cobj = serverBody.cobj; - } - if (!utils.isEmpty(bidRequest.sizes)) { - bid.width = bidRequest.sizes[0][0]; - bid.height = bidRequest.sizes[0][1]; - } - - bid.nurl = serverBody.nurl; - bid.sync = serverBody.sync; - if (bidRequest.renderMode && bidRequest.renderMode === 'banner') { - bid.mediaType = 'banner'; - if (serverBody.native) { - const adAssetsUrl = 'https://cdn.elasticad.net/native/serve/js/quantx/quantumAd/'; - let assets = serverBody.native.assets; - let link = serverBody.native.link; - - let trackers = []; - if (serverBody.native.imptrackers) { - trackers = serverBody.native.imptrackers; - } - - let jstracker = ''; - if (serverBody.native.jstracker) { - jstracker = encodeURIComponent(serverBody.native.jstracker); - } - - if (serverBody.nurl) { - trackers.push(serverBody.nurl); - } - - let ad = {}; - ad['trackers'] = trackers; - ad['jstrackers'] = jstracker; - ad['eventtrackers'] = serverBody.native.eventtrackers || []; - - for (let i = 0; i < assets.length; i++) { - let asset = assets[i]; - switch (asset['id']) { - case 1: - ad['title'] = asset['title']['text']; - break; - case 2: - ad['sponsor_logo'] = asset['img']['url']; - break; - case 3: - ad['content'] = asset['data']['value']; - break; - case 4: - ad['main_image'] = asset['img']['url']; - break; - case 6: - ad['teaser_type'] = 'vast'; - ad['video_url'] = asset['video']['vasttag']; - break; - case 10: - ad['sponsor_name'] = asset['data']['value']; - break; - case 2001: - ad['expanded_content_type'] = 'embed'; - ad['expanded_summary'] = asset['data']['value']; - break; - case 2002: - ad['expanded_content_type'] = 'vast'; - ad['expanded_summary'] = asset['data']['value']; - break; - case 2003: - ad['sponsor_url'] = asset['data']['value']; - break; - case 2004: // prism - ad['content_type'] = 'prism'; - break; - case 2005: // internal_landing_page - ad['content_type'] = 'internal_landing_page'; - ad['internal_content_link'] = asset['data']['value']; - break; - case 2006: // teaser as vast - ad['teaser_type'] = 'vast'; - ad['video_url'] = asset['data']['value']; - break; - case 2007: - ad['autoexpand_content_type'] = asset['data']['value']; - break; - case 2022: // content page - ad['content_type'] = 'full_text'; - ad['full_text'] = asset['data']['value']; - break; - } - } - - ad['action_url'] = link.url; - - if (!ad['sponsor_url']) { - ad['sponsor_url'] = ad['action_url']; - } - - ad['clicktrackers'] = []; - if (link.clicktrackers) { - ad['clicktrackers'] = link.clicktrackers; - } - - ad['main_image'] = 'https://resize-ssp.adux.com/scalecrop-290x130/' + window.btoa(ad['main_image']) + '/external'; - - bid.ad = '
' + - '
' + - '
' + - ' ' + - ' ' + - '
' + - '

' + ad['title'] + '

' + - '

' + ad['content'] + ' 

' + - ' ' + - '
' + - '' + - '' + - '' + - '
'; - } - } else { - // native - bid.mediaType = 'native'; - if (bidRequest.mediaType === 'native') { - if (serverBody.native) { - let assets = serverBody.native.assets; - let link = serverBody.native.link; - - let trackers = []; - if (serverBody.native.imptrackers) { - trackers = serverBody.native.imptrackers; - } - - if (serverBody.nurl) { - trackers.push(serverBody.nurl); - } - - let native = {}; - - for (let i = 0; i < assets.length; i++) { - let asset = assets[i]; - switch (asset['id']) { - case 1: - native.title = asset['title']['text']; - break; - case 2: - native.icon = { - url: asset['img']['url'], - width: asset['img']['w'], - height: asset['img']['h'] - }; - break; - case 3: - native.body = asset['data']['value']; - break; - case 4: - native.image = { - url: asset['img']['url'], - width: asset['img']['w'], - height: asset['img']['h'] - }; - break; - case 10: - native.sponsoredBy = asset['data']['value']; - break; - } - } - native.cta = 'read more'; - if (serverBody.language) { - native.cta = 'read more'; - } - - native.clickUrl = link.url; - native.impressionTrackers = trackers; - if (link.clicktrackers) { - native.clickTrackers = link.clicktrackers; - } - native.eventtrackers = native.eventtrackers || []; - - bid.qtx_native = utils.deepClone(serverBody.native); - bid.native = native; - } - } - } - bidResponses.push(bid); - } - - return bidResponses; - }, - - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse} serverResponse A successful response from the server - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function (syncOptions, serverResponse) { - const syncs = []; - utils._each(serverResponse, function(serverResponse) { - if (serverResponse.body && serverResponse.body.sync) { - utils._each(serverResponse.body.sync, function (pixel) { - syncs.push({ - type: 'image', - url: pixel - }); - }); - } - }); - return syncs; - } -} -registerBidder(spec); diff --git a/modules/quantumBidAdapter.md b/modules/quantumBidAdapter.md deleted file mode 100644 index 572ca9ecd37..00000000000 --- a/modules/quantumBidAdapter.md +++ /dev/null @@ -1,94 +0,0 @@ -# Overview - -``` -Module Name: Quantum Advertising Bid Adapter -Module Type: Bidder Adapter -Maintainer: support.mediareporting@adux.com -``` - -# Description - -Connects to Quantum's ssp for bids. - -# Sample Ad Unit: For Publishers -``` -var adUnits = [{ - code: 'quantum-adUnit-id-1', - sizes: [[300, 250]], - bids: [{ - bidder: 'quantum', - params: { - placementId: 21546 //quantum adUnit id - } - }] - },{ - code: 'quantum-native-adUnit-id-1', - sizes: [[0, 0]], - mediaTypes: 'native', - bids: [{ - bidder: 'quantum', - params: { - placementId: 21546 //quantum adUnit id - } - }] - }]; -``` - -# Ad Unit and Setup: For Testing - -``` - - - - - - - - - - ``` diff --git a/modules/rtbsapeBidAdapter.js b/modules/rtbsapeBidAdapter.js new file mode 100644 index 00000000000..8473ef4dbb3 --- /dev/null +++ b/modules/rtbsapeBidAdapter.js @@ -0,0 +1,142 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {OUTSTREAM} from '../src/video.js'; +import {Renderer} from '../src/Renderer.js'; +import {triggerPixel} from '../src/utils.js'; + +const BIDDER_CODE = 'rtbsape'; +const ENDPOINT = 'https://ssp-rtb.sape.ru/prebid'; +const RENDERER_SRC = 'https://cdn-rtb.sape.ru/js/player.js'; +const MATCH_SRC = 'https://www.acint.net/mc/?dp=141'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['sape'], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.video) && bid.params && bid.params.placeId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let tz = (new Date()).getTimezoneOffset() + let padInt = (v) => (v < 10 ? '0' + v : '' + v); + + return { + url: ENDPOINT, + method: 'POST', + data: { + auctionId: bidderRequest.auctionId, + requestId: bidderRequest.bidderRequestId, + bids: validBidRequests, + timezone: (tz > 0 ? '-' : '+') + padInt(Math.floor(Math.abs(tz) / 60)) + ':' + padInt(Math.abs(tz) % 60), + refererInfo: bidderRequest.refererInfo + }, + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {{data: {bids: [{mediaTypes: {banner: boolean}}]}}} bidRequest Info describing the request to the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + if (!(serverResponse.body && Array.isArray(serverResponse.body.bids))) { + return []; + } + + let bids = {}; + bidRequest.data.bids.forEach(bid => bids[bid.bidId] = bid); + + return serverResponse.body.bids.map(bid => { + let requestBid = bids[bid.requestId]; + let context = utils.deepAccess(requestBid, 'mediaTypes.video.context'); + + if (context === OUTSTREAM && (bid.vastUrl || bid.vastXml)) { + let renderer = Renderer.install({ + id: bid.requestId, + url: RENDERER_SRC, + loaded: false + }); + + let muted = utils.deepAccess(requestBid, 'params.video.playerMuted'); + if (typeof muted === 'undefined') { + muted = true; + } + + bid.playerMuted = muted; + bid.renderer = renderer + + renderer.setRender(setOutstreamRenderer); + } + + return bid; + }); + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions) { + const sync = []; + if (syncOptions.iframeEnabled) { + sync.push({ + type: 'iframe', + url: MATCH_SRC + }); + } + return sync; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function(bid) { + if (bid.nurl) { + triggerPixel(bid.nurl); + } + } +} + +/** + * Initialize RtbSape outstream player + * + * @param bid + */ +function setOutstreamRenderer(bid) { + let props = {}; + if (bid.vastUrl) { + props.url = bid.vastUrl; + } + if (bid.vastXml) { + props.xml = bid.vastXml; + } + bid.renderer.push(() => { + let player = window.sapeRtbPlayerHandler(bid.adUnitCode, bid.width, bid.height, bid.playerMuted, {singleton: true}); + props.onComplete = () => player.destroy(); + props.onError = () => player.destroy(); + player.addSlot(props); + }); +} + +registerBidder(spec); diff --git a/modules/rtbsapeBidAdapter.md b/modules/rtbsapeBidAdapter.md new file mode 100644 index 00000000000..6b1afe3867d --- /dev/null +++ b/modules/rtbsapeBidAdapter.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: RtbSape Bid Adapter +Module Type: Bidder Adapter +Maintainer: sergey@sape.ru +``` + +# Description +Our module makes it easy to integrate RtbSape demand sources into your website. + +Supported Ad format: +* Banner +* Video (instream and outstream) + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'rtbsape', + params: { + placeId: 553307 + } + }] + }, + // Video adUnit + { + code: 'video-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [600, 340] + } + }, + bids: [{ + bidder: 'rtbsape', + params: { + placeId: 553309 + } + }] + } +]; +``` diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 00ad14dd316..6defd7b45ae 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -195,15 +195,23 @@ function sendMessage(auctionId, bidWonId) { // pick our of top level floor data we want to send! if (auctionCache.floorData) { - auction.floors = utils.pick(auctionCache.floorData, [ - 'location', - 'modelName', () => auctionCache.floorData.modelVersion, - 'skipped', - 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), - 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), - 'skipRate', skipRate => !isNaN(skipRate) ? skipRate : 0, - 'fetchStatus' - ]); + if (auctionCache.floorData.location === 'noData') { + auction.floors = utils.pick(auctionCache.floorData, [ + 'location', + 'fetchStatus' + ]); + } else { + auction.floors = utils.pick(auctionCache.floorData, [ + 'location', + 'modelVersion as modelName', + 'skipped', + 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), + 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), + 'skipRate', + 'fetchStatus', + 'floorProvider as provider' + ]); + } } if (serverConfig) { @@ -269,7 +277,7 @@ function getBidPrice(bid) { } } -export function parseBidResponse(bid, previousBidResponse) { +export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { // The current bidResponse for this matching requestId/bidRequestId let responsePrice = getBidPrice(bid) // we need to compare it with the previous one (if there was one) @@ -368,8 +376,9 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { cacheEntry.bids = {}; cacheEntry.bidsWon = {}; cacheEntry.referrer = args.bidderRequests[0].refererInfo.referer; - if (utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData')) { - cacheEntry.floorData = {...utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData')}; + const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + if (floorData) { + cacheEntry.floorData = {...floorData}; } cache.auctions[args.auctionId] = cacheEntry; break; diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index cd621010a9b..0c563987331 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -332,7 +332,16 @@ export const spec = { */ const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); + utils.deepSetValue(data.imp[0].ext, 'context.data.pbadslot', pbAdSlot); + } + + /** + * GAM Ad Unit + * @type {(string|undefined)} + */ + const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); + if (typeof gamAdUnit === 'string' && gamAdUnit) { + utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', gamAdUnit); } // if storedAuctionResponse has been set, pass SRID @@ -593,7 +602,16 @@ export const spec = { */ const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { - data['tg_i.dfp_ad_unit_code'] = pbAdSlot.replace(/^\/+/, ''); + data['tg_i.pbadslot'] = pbAdSlot.replace(/^\/+/, ''); + } + + /** + * GAM Ad Unit + * @type {(string|undefined)} + */ + const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); + if (typeof gamAdUnit === 'string' && gamAdUnit) { + data['tg_i.dfp_ad_unit_code'] = gamAdUnit.replace(/^\/+/, ''); } // digitrust properties diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js new file mode 100755 index 00000000000..0e53d2a300d --- /dev/null +++ b/modules/saambaaBidAdapter.js @@ -0,0 +1,401 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const ADAPTER_VERSION = '1.0'; +const BIDDER_CODE = 'saambaa'; + +export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip']; +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +let pubid = ''; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bidRequest) { + if (typeof bidRequest != 'undefined') { + if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } + if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } + return true; + } else { return false; } + }, + + buildRequests(bids, bidderRequest) { + let requests = []; + let videoBids = bids.filter(bid => isVideoBidValid(bid)); + let bannerBids = bids.filter(bid => isBannerBidValid(bid)); + videoBids.forEach(bid => { + pubid = getVideoBidParam(bid, 'pubid'); + requests.push({ + method: 'POST', + url: VIDEO_ENDPOINT + pubid, + data: createVideoRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + + bannerBids.forEach(bid => { + pubid = getBannerBidParam(bid, 'pubid'); + + requests.push({ + method: 'POST', + url: BANNER_ENDPOINT + pubid, + data: createBannerRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + return requests; + }, + + interpretResponse(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (response !== null && utils.isEmpty(response) == false) { + if (isVideoBid(bidRequest)) { + let bidResponse = { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: VIDEO, + netRevenue: true + } + + if (response.seatbid[0].bid[0].adm) { + bidResponse.vastXml = response.seatbid[0].bid[0].adm; + bidResponse.adResponse = { + content: response.seatbid[0].bid[0].adm + }; + } else { + bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; + } + + return bidResponse; + } else { + return { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: BANNER, + netRevenue: true + } + } + } + } +}; + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +function getVideoBidParam(bid, key) { + return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key); +} + +function getBannerBidParam(bid, key) { + return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key); +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +function getOsVersion() { + let clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); + return cs ? cs.s : 'unknown'; +} + +function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +function parseSizes(sizes) { + return utils.parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +function getVideoSizes(bid) { + return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +function getBannerSizes(bid) { + return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +function getVideoTargetingParams(bid) { + return Object.keys(Object(bid.params.video)) + .filter(param => includes(VIDEO_TARGETING, param)) + .reduce((obj, param) => { + obj[ param ] = bid.params.video[ param ]; + return obj; + }, {}); +} + +function createVideoRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + let paramSize = getVideoBidParam(bid, 'size'); + let sizes = []; + + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getVideoSizes(bid); + } + const firstSize = getFirstSize(sizes); + + let video = getVideoTargetingParams(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1, + 'os': getOsVersion() + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getVideoBidParam(bid, 'placement'); + let floor = getVideoBidParam(bid, 'floor'); + if (floor == null) { floor = 0.5; } + + for (let j = 0; j < sizes.length; j++) { + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'video': Object.assign({ + 'id': utils.generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, video) + + }); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} + +function getTopWindowLocation(bidderRequest) { + let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); +} + +function createBannerRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + + let paramSize = getBannerBidParam(bid, 'size'); + let sizes = []; + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getBannerSizes(bid); + } + + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1 + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getBannerBidParam(bid, 'placement'); + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + + let floor = getBannerBidParam(bid, 'floor'); + if (floor == null) { floor = 0.1; } + + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'banner': { + 'id': utils.generateUUID(), + 'pos': 0, + 'w': size['w'], + 'h': size['h'] + } + }); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} +registerBidder(spec); diff --git a/modules/saambaaBidAdapter.md b/modules/saambaaBidAdapter.md new file mode 100755 index 00000000000..2d391da7628 --- /dev/null +++ b/modules/saambaaBidAdapter.md @@ -0,0 +1,69 @@ +# Overview + +``` +Module Name: Saambaa Bidder Adapter +Module Type: Bidder Adapter +Maintainer: matt.voigt@saambaa.com +``` + +# Description + +Connects to Saambaa exchange for bids. + +Saambaa bid adapter supports Banner and Video ads currently. + +For more informatio + +# Sample Display Ad Unit: For Publishers +```javascript + +var displayAdUnit = [ +{ + code: 'display', + mediaTypes: { + banner: { + sizes: [[300, 250],[320, 50]] + } + } + bids: [{ + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: '320x50' + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +```javascript + +var videoAdUnit = { + code: 'video', + sizes: [320,480], + mediaTypes: { + video: { + playerSize : [[320, 480]], + context: 'instream' + } + }, + bids: [ + { + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: "320x480", + video: { + id: 123, + skip: 1, + mimes : ['video/mp4', 'application/javascript'], + playbackmethod : [2,6], + maxduration: 30 + } + } + } + ] + }; +``` \ No newline at end of file diff --git a/modules/serverbidBidAdapter.md b/modules/serverbidBidAdapter.md deleted file mode 100644 index 87b51e665e2..00000000000 --- a/modules/serverbidBidAdapter.md +++ /dev/null @@ -1,44 +0,0 @@ -# Overview - -Module Name: Serverbid Bid Adapter - -Module Type: Bid Adapter - -Maintainer: jgrimes@serverbid.com, jswart@serverbid.com - -# Description - -Connects to Serverbid for receiving bids from configured demand sources. - -# Test Parameters -```javascript - var adUnits = [ - { - code: 'test-ad-1', - sizes: [[300, 250]], - bids: [ - { - bidder: 'serverbid', - params: { - networkId: '9969', - siteId: '980639' - } - } - ] - }, - { - code: 'test-ad-2', - sizes: [[300, 250]], - bids: [ - { - bidder: 'serverbid', - params: { - networkId: '9969', - siteId: '980639', - zoneIds: [178503] - } - } - ] - } - ]; -``` diff --git a/modules/sharedIdSystem.md b/modules/sharedIdSystem.md index acb076ed97f..a4541c16c49 100644 --- a/modules/sharedIdSystem.md +++ b/modules/sharedIdSystem.md @@ -13,7 +13,7 @@ ex: $ gulp build --modules=userId,sharedIdSystem Individual params may be set for the Shared ID User ID Submodule. ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: 'sharedId', params: { diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 20ab640990b..4df537e0eb3 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -1,6 +1,7 @@ /** - * This modules adds support for the new Size Mapping spec described here. https://github.com/prebid/Prebid.js/issues/4129 - * This implementation replaces global sizeConfig with a adUnit/bidder level sizeConfig with support for labels. + * This module adds support for the new size mapping spec, Advanced Size Mapping. It's documented here. https://github.com/prebid/Prebid.js/issues/4129 + * The implementation is an alternative to global sizeConfig. It introduces 'Ad Unit' & 'Bidder' level sizeConfigs and also supports 'labels' for conditional + * rendering. Read full API documentation on Prebid.org, http://prebid.org/dev-docs/modules/sizeMappingV2.html */ import * as utils from '../src/utils.js'; @@ -8,11 +9,9 @@ import { processNativeAdUnitParams } from '../src/native.js'; import { adunitCounter } from '../src/adUnits.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getHook } from '../src/hook.js'; -import { - adUnitSetupChecks -} from '../src/prebid.js'; +import { adUnitSetupChecks } from '../src/prebid.js'; -// allows for sinon.spy, sinon.stub, etc to unit test calls made to these functions internally +// Allows for stubbing of these functions while writing unit tests. export const internal = { checkBidderSizeConfigFormat, getActiveSizeBucket, @@ -22,9 +21,12 @@ export const internal = { isLabelActivated }; -// 'sizeMappingInternalStore' contains information whether a particular auction is using size mapping V2 (the new size mapping spec), -// and it also contains additional information on each adUnit, as such, mediaTypes, activeViewport, etc. -// This information is required by the 'getBids' function. +/* + 'sizeMappingInternalStore' contains information on, whether a particular auction is using size mapping V2 (the new size mapping spec), + and it also contains additional information on each adUnit, such as, mediaTypes, activeViewport, etc. This information is required by + the 'getBids' function. +*/ + export const sizeMappingInternalStore = createSizeMappingInternalStore(); function createSizeMappingInternalStore() { @@ -46,8 +48,10 @@ function createSizeMappingInternalStore() { } } -// returns "true" if atleast one of the adUnit in the adUnits array has declared a Ad Unit or(and) Bidder level sizeConfig -// returns "false" otherwise +/* + Returns "true" if at least one of the adUnits in the adUnits array is using an Ad Unit and/or Bidder level sizeConfig, + otherwise, returns "false." +*/ export function isUsingNewSizeMapping(adUnits) { let isUsingSizeMappingBool = false; adUnits.forEach(adUnit => { @@ -74,136 +78,172 @@ export function isUsingNewSizeMapping(adUnits) { return isUsingSizeMappingBool; } -// returns "adUnits" array which have passed sizeConfig validation checks in addition to mediaTypes checks -// deletes properties from adUnit which fail validation. +/** + This hooked function executes before the function 'checkAdUnitSetup', that is defined in /src/prebid.js. It's necessary to run this funtion before + because it applies a series of checks in order to determine the correctness of the 'sizeConfig' array, which, the original 'checkAdUnitSetup' function + does not recognize. + @params {Array} adUnits + @returns {Array} validateAdUnits - Unrecognized properties are deleted. +*/ export function checkAdUnitSetupHook(adUnits) { - return adUnits.filter(adUnit => { + const validateSizeConfig = function (mediaType, sizeConfig, adUnitCode) { + let isValid = true; + const associatedProperty = { + banner: 'sizes', + video: 'playerSize', + native: 'active' + } + const propertyName = associatedProperty[mediaType]; + const conditionalLogMessages = { + banner: 'Removing mediaTypes.banner from ad unit.', + video: 'Removing mediaTypes.video.sizeConfig from ad unit.', + native: 'Removing mediaTypes.native.sizeConfig from ad unit.' + } + if (Array.isArray(sizeConfig)) { + sizeConfig.forEach((config, index) => { + const keys = Object.keys(config); + /* + Check #1 (Applies to 'banner', 'video' and 'native' media types.) + Verify that all config objects include 'minViewPort' and 'sizes' property. + If they do not, return 'false'. + */ + if (!(includes(keys, 'minViewPort') && includes(keys, propertyName))) { + utils.logError(`Ad unit ${adUnitCode}: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + return; + } + /* + Check #2 (Applies to 'banner', 'video' and 'native' media types.) + Verify that 'config.minViewPort' property is in [width, height] format. + If not, return false. + */ + if (!utils.isArrayOfNums(config.minViewPort, 2)) { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'minViewPort' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false + return; + } + /* + Check #3 (Applies only to 'banner' and 'video' media types.) + Verify that 'config.sizes' (in case of banner) or 'config.playerSize' (in case of video) + property is in [width, height] format. If not, return 'false'. + */ + if (mediaType === 'banner' || mediaType === 'video') { + let showError = false; + if (Array.isArray(config[propertyName])) { + const validatedSizes = adUnitSetupChecks.validateSizes(config[propertyName]); + if (config[propertyName].length > 0 && validatedSizes.length === 0) { + isValid = false; + showError = true; + } + } else { + // Either 'sizes' or 'playerSize' is not declared as an array, which makes it invalid by default. + isValid = false; + showError = true; + } + if (showError) { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of '${propertyName}' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + return; + } + } + /* + Check #4 (Applies only to 'native' media type) + Verify that 'config.active' is a 'boolean'. + If not, return 'false'. + */ + if (mediaType === 'native') { + if (typeof config[propertyName] !== 'boolean') { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'active' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + } + } + }); + } else { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'sizeConfig' in 'mediaTypes.${mediaType}.sizeConfig'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + return isValid; + } + + // If all checks have passed, isValid should equal 'true' + return isValid; + } + const validatedAdUnits = []; + adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + let validatedBanner, validatedVideo, validatedNative; if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); - return false; + return; } - if (mediaTypes.banner) { - const banner = mediaTypes.banner; - if (banner.sizes) { - adUnitSetupChecks.validateBannerMediaType(adUnit); - } else if (banner.sizeConfig) { - if (Array.isArray(banner.sizeConfig)) { - let deleteBannerMediaType = false; - banner.sizeConfig.forEach((config, index) => { - // verify if all config objects include "minViewPort" and "sizes" property. - // if not, remove the mediaTypes.banner object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'sizes'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] is missing required property minViewPort or sizes or both.`); - deleteBannerMediaType = true; - return; - } - - // check if the config.sizes property is in [w, h] format, if yes, change it to [[w, h]] format. - const bannerSizes = adUnitSetupChecks.validateSizes(config.sizes); - if (utils.isArrayOfNums(config.minViewPort, 2)) { - if (config.sizes.length > 0 && bannerSizes.length > 0) { - config.sizes = bannerSizes; - } else if (config.sizes.length === 0) { - // If a size bucket doesn't have any sizes, sizes is an empty array, i.e. sizes: []. This check takes care of that. - config.sizes = [config.sizes]; - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has propery sizes declared with invalid value. Please ensure the sizes are listed like: [[300, 250], ...] or like: [] if no sizes are present for that size bucket.`); - deleteBannerMediaType = true; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); - deleteBannerMediaType = true; + if (mediaTypes.banner.sizes) { + // Ad unit is using 'mediaTypes.banner.sizes' instead of the new property 'sizeConfig'. Apply the old checks! + validatedBanner = adUnitSetupChecks.validateBannerMediaType(adUnit); + } else if (mediaTypes.banner.sizeConfig) { + // Ad unit is using the 'sizeConfig' property, 'mediaTypes.banner.sizeConfig'. Apply the new checks! + validatedBanner = utils.deepClone(adUnit); + const isBannerValid = validateSizeConfig('banner', mediaTypes.banner.sizeConfig, adUnit.code); + if (!isBannerValid) { + delete validatedBanner.mediaTypes.banner; + } else { + /* + Make sure 'sizes' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [360, 400] becomes [[360, 400]] + */ + validatedBanner.mediaTypes.banner.sizeConfig.forEach(config => { + if (!Array.isArray(config.sizes[0])) { + config.sizes = [config.sizes]; } }); - if (deleteBannerMediaType) { - utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.banner has been removed due to error in sizeConfig.`); - delete adUnit.mediaTypes.banner; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig is NOT an Array. Removing the invalid object mediaTypes.banner from Ad Unit.`); - delete adUnit.mediaTypes.banner; } } else { - utils.logError('Detected a mediaTypes.banner object did not include required property sizes or sizeConfig. Removing invalid mediaTypes.banner object from Ad Unit.'); - delete adUnit.mediaTypes.banner; + // Ad unit is invalid since it's mediaType property does not have either 'sizes' or 'sizeConfig' declared. + utils.logError(`Ad unit ${adUnit.code}: 'mediaTypes.banner' does not contain either 'sizes' or 'sizeConfig' property. Removing 'mediaTypes.banner' from ad unit.`); + validatedBanner = utils.deepClone(adUnit); + delete validatedBanner.mediaTypes.banner; } } if (mediaTypes.video) { - const video = mediaTypes.video; - if (video.playerSize) { - adUnitSetupChecks.validateVideoMediaType(adUnit); - } else if (video.sizeConfig) { - if (Array.isArray(video.sizeConfig)) { - let deleteVideoMediaType = false; - video.sizeConfig.forEach((config, index) => { - // verify if all config objects include "minViewPort" and "playerSize" property. - // if not, remove the mediaTypes.video object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'playerSize'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] is missing required property minViewPort or playerSize or both. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); - deleteVideoMediaType = true; - return; - } - // check if the config.playerSize property is in [w, h] format, if yes, change it to [[w, h]] format. - let tarPlayerSizeLen = (typeof config.playerSize[0] === 'number') ? 2 : 1; - const videoSizes = adUnitSetupChecks.validateSizes(config.playerSize, tarPlayerSizeLen); - if (utils.isArrayOfNums(config.minViewPort, 2)) { - if (tarPlayerSizeLen === 2) { - utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); - } - if (config.playerSize.length > 0 && videoSizes.length > 0) { - config.playerSize = videoSizes; - } else if (config.playerSize.length === 0) { - // If a size bucket doesn't have any playerSize, playerSize is an empty array, i.e. playerSize: []. This check takes care of that. - config.playerSize = [config.playerSize]; - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has propery playerSize declared with invalid value. Please ensure the playerSize is listed like: [640, 480] or like: [] if no playerSize is present for that size bucket.`); - deleteVideoMediaType = true; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); - deleteVideoMediaType = true; + if (mediaTypes.video.playerSize) { + // Ad unit is using 'mediaTypes.video.playerSize' instead of the new property 'sizeConfig'. Apply the old checks! + validatedVideo = validatedBanner ? adUnitSetupChecks.validateVideoMediaType(validatedBanner) : adUnitSetupChecks.validateVideoMediaType(adUnit); + } else if (mediaTypes.video.sizeConfig) { + // Ad unit is using the 'sizeConfig' property, 'mediaTypes.video.sizeConfig'. Apply the new checks! + validatedVideo = validatedBanner || utils.deepClone(adUnit); + const isVideoValid = validateSizeConfig('video', mediaTypes.video.sizeConfig, adUnit.code); + if (!isVideoValid) { + delete validatedVideo.mediaTypes.video.sizeConfig; + } else { + /* + Make sure 'playerSize' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [640, 400] becomes [[640, 400]] + */ + validatedVideo.mediaTypes.video.sizeConfig.forEach(config => { + if (!Array.isArray(config.playerSize[0])) { + config.playerSize = [config.playerSize]; } }); - if (deleteVideoMediaType) { - utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig has been removed due to error in sizeConfig.`); - delete adUnit.mediaTypes.video.sizeConfig; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig is NOT an Array. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.video.sizeConfig; } } } if (mediaTypes.native) { - const native = mediaTypes.native; - adUnitSetupChecks.validateNativeMediaType(adUnit); + // Apply the old native checks + validatedNative = validatedVideo ? adUnitSetupChecks.validateNativeMediaType(validatedVideo) : validatedBanner ? adUnitSetupChecks.validateNativeMediaType(validatedBanner) : adUnitSetupChecks.validateNativeMediaType(adUnit); + // Apply the new checks if 'mediaTypes.native.sizeConfig' detected if (mediaTypes.native.sizeConfig) { - native.sizeConfig.forEach(config => { - // verify if all config objects include "minViewPort" and "active" property. - // if not, remove the mediaTypes.native object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'active'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig is missing required property minViewPort or active or both. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.native.sizeConfig; - } - - if (!(utils.isArrayOfNums(config.minViewPort, 2) && typeof config.active === 'boolean')) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.native.sizeConfig; - } - }); + const isNativeValid = validateSizeConfig('native', mediaTypes.native.sizeConfig, adUnit.code); + if (!isNativeValid) { + delete validatedNative.mediaTypes.native.sizeConfig; + } } } - return true; + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + validatedAdUnits.push(validatedAdUnit); }); + return validatedAdUnits; } getHook('checkAdUnitSetup').before(function (fn, adUnits) { diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js new file mode 100644 index 00000000000..ce0edb1e19c --- /dev/null +++ b/modules/smaatoBidAdapter.js @@ -0,0 +1,268 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'smaato'; +const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; +const CLIENT = 'prebid_js_$prebid.version$_1.0' + +/** +* Transform BidRequest to OpenRTB-formatted BidRequest Object +* @param {Array} validBidRequests +* @param {any} bidderRequest +* @returns {string} +*/ +const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { + /** + * Turn incoming prebid sizes into openRtb format mapping. + * @param {*} sizes in format [[10, 10], [20, 20]] + * @returns array of openRtb format mappings [{w: 10, h: 10}, {w: 20, h: 20}] + */ + const parseSizes = (sizes) => { + return sizes.map((size) => { + return {w: size[0], h: size[1]}; + }) + } + + const imp = validBidRequests.map(br => { + const bannerMediaType = utils.deepAccess(br, 'mediaTypes.banner'); + const videoMediaType = utils.deepAccess(br, 'mediaTypes.video'); + let result = { + id: br.bidId, + tagid: utils.deepAccess(br, 'params.adspaceId') + } + + if (bannerMediaType) { + const sizes = parseSizes(utils.getAdUnitSizes(br)); + result.banner = { + w: sizes[0].w, + h: sizes[0].h, + format: sizes + } + } + + if (videoMediaType) { + result.video = { + mimes: videoMediaType.mimes, + minduration: videoMediaType.minduration, + startdelay: videoMediaType.startdelay, + linearity: videoMediaType.linearity, + w: videoMediaType.playerSize[0][0], + h: videoMediaType.playerSize[0][1], + maxduration: videoMediaType.maxduration, + skip: videoMediaType.skip, + protocols: videoMediaType.protocols, + ext: { + rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0 + }, + skipmin: videoMediaType.skipmin, + api: videoMediaType.api + } + } + + return result; + }); + + const request = { + id: bidderRequest.auctionId, + at: 1, + imp, + cur: ['USD'], + tmax: bidderRequest.timeout, + site: { + id: window.location.hostname, + publisher: { + id: utils.deepAccess(validBidRequests[0], 'params.publisherId') + }, + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo.referer + }, + device: { + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + h: screen.height, + w: screen.width + }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, + ext: { + client: CLIENT + } + }; + + Object.assign(request.user, config.getConfig('fpd.user')); + Object.assign(request.site, config.getConfig('fpd.context')); + + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { + utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent !== undefined) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + utils.logInfo('[SMAATO] OpenRTB Request:', request); + return JSON.stringify(request); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return typeof bid.params === 'object' && + typeof bid.params.publisherId === 'string' && + typeof bid.params.adspaceId === 'string'; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + utils.logInfo('[SMAATO] Client version:', CLIENT); + return { + method: 'POST', + url: validBidRequests[0].params.endpoint || SMAATO_ENDPOINT, + data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), + options: { + withCredentials: true, + crossOrigin: true, + } + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse, bidRequest) => { + // response is empty (HTTP 204) + if (utils.isEmpty(serverResponse.body)) { + utils.logInfo('[SMAATO] Empty response body HTTP 204, no bids'); + return []; // no bids + } + + let serverResponseHeaders = serverResponse.headers; + const smtAdType = serverResponseHeaders.get('X-SMT-ADTYPE'); + + const smtExpires = serverResponseHeaders.get('X-SMT-Expires'); + let ttlSec = 300; + utils.logInfo('[SMAATO] Expires:', smtExpires); + if (smtExpires) { + ttlSec = Math.floor((smtExpires - Date.now()) / 1000); + } + + const res = serverResponse.body; + utils.logInfo('[SMAATO] OpenRTB Response:', res); + + var bids = []; + res.seatbid.forEach(sb => { + sb.bid.forEach(b => { + let resultingBid = { + requestId: b.impid, + cpm: b.price || 0, + width: b.w, + height: b.h, + ttl: ttlSec, + creativeId: b.crid, + dealId: b.dealid || null, + netRevenue: true, + currency: res.cur, + meta: { + advertiserDomains: b.adomain, + networkName: b.bidderName, + agencyId: sb.seat + } + }; + + switch (smtAdType) { + case 'Img': + resultingBid.ad = createImgAd(b.adm); + resultingBid.meta.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Richmedia': + resultingBid.ad = createRichmediaAd(b.adm); + resultingBid.meta.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Video': + resultingBid.vastXml = b.adm; + resultingBid.meta.mediaType = VIDEO; + bids.push(resultingBid); + break; + default: + utils.logInfo('[SMAATO] Invalid ad type:', smtAdType); + } + }); + }); + + utils.logInfo('[SMAATO] Prebid bids:', bids); + return bids; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + const syncs = [] + return syncs; + } +} +registerBidder(spec); + +const createImgAd = (adm) => { + const image = JSON.parse(adm).image; + + let clickEvent = ''; + image.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
`; + + image.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; + +const createRichmediaAd = (adm) => { + const rich = JSON.parse(adm).richmedia; + let clickEvent = ''; + rich.clicktrackers.forEach(src => { + clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + + let markup = `
${rich.mediadata.content}`; + + rich.impressiontrackers.forEach(src => { + markup += ``; + }); + + return markup + '
'; +}; diff --git a/modules/smaatoBidAdapter.md b/modules/smaatoBidAdapter.md new file mode 100644 index 00000000000..d26d7ecf64e --- /dev/null +++ b/modules/smaatoBidAdapter.md @@ -0,0 +1,64 @@ +# Overview + +``` +Module Name: Smaato Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@smaato.com +``` + +# Description + +The Smaato adapter requires setup and approval from the Smaato team, even for existing Smaato publishers. Please reach out to your account team or prebid@smaato.com for more information. + +# Test Parameters + +For banner adunits: + +``` +var adUnits = [{ + "code": "banner-unit", + "mediaTypes": { + "banner": { + "sizes": [320, 50] + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` + +For video adunits: + +``` +var adUnits = [{ + "code": "video unit", + "mediaTypes": { + "video": { + "context": "instream", + "playerSize": [640, 480], + "mimes": ["video/mp4"], + "minduration": 5, + "maxduration": 30, + "startdelay": 0, + "linearity": 1, + "protocols": [7], + "skip": 1, + "skipmin": 5, + "api": [7], + "ext": {"rewarded": 0} + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 1733a176eba..a8d874c57e9 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -331,6 +331,11 @@ export const spec = { height: spotxBid.h }; + bid.meta = bid.meta || {}; + if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { + bid.meta.advertiserDomains = spotxBid.adomain; + } + const context1 = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); const context2 = utils.deepAccess(currentBidRequest, 'params.ad_unit'); if (context1 == 'outstream' || context2 == 'outstream') { diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 8f069f551ee..abbae1e7354 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import { getAdUnitSizes, logWarn } from '../src/utils.js'; +import { getAdUnitSizes, logWarn, deepSetValue } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -85,6 +85,11 @@ export const spec = { } }); + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(openRtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (openRtbBidRequest.imp.length && seatId) { return { method: 'POST', @@ -167,7 +172,7 @@ export const spec = { .filter(param => includes(VIDEO_PARAMS, param) && sourceObj[param] !== null && (!isNaN(parseInt(sourceObj[param], 10)) || !(sourceObj[param].length < 1))) .forEach(param => destObj[param] = Array.isArray(sourceObj[param]) ? sourceObj[param] : parseInt(sourceObj[param], 10)); }, - interpretResponse: function(serverResponse) { + interpretResponse: function(serverResponse, bidRequest) { const updateMacros = (bid, r) => { return r ? r.replace(/\${AUCTION_PRICE}/g, bid.price) : r; }; @@ -184,8 +189,33 @@ export const spec = { seatbid.bid.forEach(bid => { const creative = updateMacros(bid, bid.adm); const nurl = updateMacros(bid, bid.nurl); - const [, impType, impid, width, height] = bid.impid.match(/^([vb])(.*)-(.*)x(.*)$/); - const isVideo = impType == 'v'; + const [, impType, impid] = bid.impid.match(/^([vb])(.*)$/); + let height = bid.h; + let width = bid.w; + const isVideo = impType === 'v'; + const isBanner = impType === 'b'; + if ((!height || !width) && bidRequest.data && bidRequest.data.imp && bidRequest.data.imp.length > 0) { + bidRequest.data.imp.forEach(req => { + if (bid.impid === req.id) { + if (isVideo) { + height = req.video.h; + width = req.video.w; + } else if (isBanner) { + let bannerHeight = 1; + let bannerWidth = 1; + if (req.banner.format && req.banner.format.length > 0) { + bannerHeight = req.banner.format[0].h; + bannerWidth = req.banner.format[0].w; + } + height = bannerHeight; + width = bannerWidth; + } else { + height = 1; + width = 1; + } + } + }); + } const bidObj = { requestId: impid, adId: bid.id.replace(/~/g, '-'), diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index dbeed4aceae..08ae1854669 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -43,6 +43,7 @@ export const spec = { referrer: getReferrerInfo(bidderRequest), pageReferrer: document.referrer, networkBandwidth: getConnectionDownLink(window.navigator), + timeToFirstByte: getTimeToFirstByte(window), data: bids, deviceWidth: screen.width, hb_version: '$prebid.version$' @@ -123,6 +124,35 @@ function getConnectionDownLink(nav) { return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : ''; } +function getTimeToFirstByte(win) { + const performance = win.performance || win.webkitPerformance || win.msPerformance || win.mozPerformance; + + const ttfbWithTimingV2 = performance && + typeof performance.getEntriesByType === 'function' && + Object.prototype.toString.call(performance.getEntriesByType) === '[object Function]' && + performance.getEntriesByType('navigation')[0] && + performance.getEntriesByType('navigation')[0].responseStart && + performance.getEntriesByType('navigation')[0].requestStart && + performance.getEntriesByType('navigation')[0].responseStart >= 0 && + performance.getEntriesByType('navigation')[0].requestStart >= 0 && + Math.round( + performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart + ); + + if (ttfbWithTimingV2) { + return ttfbWithTimingV2.toString(); + } + + const ttfbWithTimingV1 = performance && + performance.timing.responseStart && + performance.timing.requestStart && + performance.timing.responseStart >= 0 && + performance.timing.requestStart >= 0 && + performance.timing.responseStart - performance.timing.requestStart; + + return ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; +} + function findGdprStatus(gdprApplies, gdprData, apiVersion) { let status = gdprStatus.GDPR_APPLIES_PUBLISHER if (gdprApplies) { diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 74c97f34b74..acc20f6b183 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -284,6 +284,11 @@ function createBid(status, reqBid, response, width, height, bidderCode) { }); } + bid.meta = bid.meta || {}; + if (response && response.adomain && response.adomain.length > 0) { + bid.meta.advertiserDomains = response.adomain; + } + return bid; } diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index af904aedc11..d6b1f95351d 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,4 +1,4 @@ -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; @@ -11,9 +11,13 @@ let consentString = null; export const tripleliftAdapterSpec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid: function(bid) { - return (typeof bid.params.inventoryCode !== 'undefined'); + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function (bid) { + if (bid.mediaTypes.video) { + let video = _getORTBVideo(bid); + if (!video.w || !video.h) return false; + } + return typeof bid.params.inventoryCode !== 'undefined'; }, buildRequests: function(bidRequests, bidderRequest) { @@ -107,15 +111,18 @@ function _getSyncType(syncOptions) { function _buildPostBody(bidRequests) { let data = {}; let { schain } = bidRequests[0]; - data.imp = bidRequests.map(function(bid, index) { - return { + data.imp = bidRequests.map(function(bidRequest, index) { + let imp = { id: index, - tagid: bid.params.inventoryCode, - floor: _getFloor(bid), - banner: { - format: _sizes(bid.sizes) - } + tagid: bidRequest.params.inventoryCode, + floor: _getFloor(bidRequest) + }; + if (bidRequest.mediaTypes.video) { + imp.video = _getORTBVideo(bidRequest); + } else if (bidRequest.mediaTypes.banner) { + imp.banner = { format: _sizes(bidRequest.sizes) }; }; + return imp; }); let eids = [ @@ -138,6 +145,17 @@ function _buildPostBody(bidRequests) { return data; } +function _getORTBVideo(bidRequest) { + // give precedent to mediaTypes.video + let video = { ...bidRequest.params.video, ...bidRequest.mediaTypes.video }; + if (!video.w) video.w = video.playerSize[0][0]; + if (!video.h) video.h = video.playerSize[0][1]; + if (video.context === 'instream') video.placement = 1; + // clean up oRTB object + delete video.playerSize; + return video; +} + function _getFloor (bid) { let floor = null; if (typeof bid.getFloor === 'function') { @@ -207,10 +225,11 @@ function _buildResponseObject(bidderRequest, bid) { let height = bid.height || 1; let dealId = bid.deal_id || ''; let creativeId = bid.crid || ''; + let breq = bidderRequest.bids[bid.imp_id]; if (bid.cpm != 0 && bid.ad) { bidResponse = { - requestId: bidderRequest.bids[bid.imp_id].bidId, + requestId: breq.bidId, cpm: bid.cpm, width: width, height: height, @@ -220,7 +239,12 @@ function _buildResponseObject(bidderRequest, bid) { dealId: dealId, currency: 'USD', ttl: 300, - tl_source: bid.tl_source, + tl_source: bid.tl_source + }; + + if (breq.mediaTypes.video) { + bidResponse.vastXml = bid.ad; + bidResponse.mediaType = 'video'; }; }; return bidResponse; diff --git a/modules/tripleliftBidAdapter.md b/modules/tripleliftBidAdapter.md index d5f88a2bece..03dcee3b980 100644 --- a/modules/tripleliftBidAdapter.md +++ b/modules/tripleliftBidAdapter.md @@ -58,5 +58,25 @@ var adUnits = [{ floor: 0 } }] +}, { + code: 'instream-div-1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + } + }, + bids: [ + { + bidder: 'triplelift', + params: { + inventoryCode: 'instream_test', + video: { + mimes: ['video/mp4'], + w: 640, + h: 480, + }, + } + }] }]; ``` diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index 6ead453b622..743cb07b21e 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,8 +2,9 @@ * Adapter to send bids to Undertone */ -import { parseUrl } from '../src/utils.js'; +import { deepAccess, parseUrl } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'undertone'; const URL = 'https://hb.undertone.com/hb'; @@ -73,6 +74,7 @@ function getBannerCoords(id) { export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (bid && bid.params && bid.params.publisherId) { bid.params.publisherId = parseInt(bid.params.publisherId); @@ -120,8 +122,20 @@ export const spec = { sizes: bidReq.sizes, params: bidReq.params }; + const videoMediaType = deepAccess(bidReq, 'mediaTypes.video'); + if (videoMediaType) { + bid.video = { + playerSize: deepAccess(bidReq, 'mediaTypes.video.playerSize') || null, + streamType: deepAccess(bidReq, 'mediaTypes.video.context') || null, + playbackMethod: deepAccess(bidReq, 'params.video.playbackMethod') || null, + maxDuration: deepAccess(bidReq, 'params.video.maxDuration') || null, + skippable: deepAccess(bidReq, 'params.video.skippable') || null + }; + bid.mediaType = 'video'; + } payload['x-ut-hb-params'].push(bid); }); + return { method: 'POST', url: reqUrl, @@ -144,9 +158,14 @@ export const spec = { creativeId: bidRes.adId, currency: bidRes.currency, netRevenue: bidRes.netRevenue, - ttl: bidRes.ttl || 360, - ad: bidRes.ad + ttl: bidRes.ttl || 360 }; + if (bidRes.mediaType && bidRes.mediaType === 'video') { + bid.vastXml = bidRes.ad; + bid.mediaType = bidRes.mediaType; + } else { + bid.ad = bidRes.ad + } bids.push(bid); } }); diff --git a/modules/userId/eids.js b/modules/userId/eids.js index d3f32fbdd31..72be98eca50 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -35,9 +35,29 @@ const USER_IDS_CONFIG = { }, // parrableId - 'parrableid': { + 'parrableId': { source: 'parrable.com', - atype: 1 + atype: 1, + getValue: function(parrableId) { + if (parrableId.eid) { + return parrableId.eid; + } + if (parrableId.ccpaOptout) { + // If the EID was suppressed due to a non consenting ccpa optout then + // we still wish to provide this as a reason to the adapters + return ''; + } + return null; + }, + getUidExt: function(parrableId) { + const extendedData = utils.pick(parrableId, [ + 'ibaOptout', + 'ccpaOptout' + ]); + if (Object.keys(extendedData).length) { + return extendedData; + } + } }, // identityLink @@ -74,19 +94,6 @@ const USER_IDS_CONFIG = { atype: 1, }, - // DigiTrust - 'digitrustid': { - getValue: function (data) { - var id = null; - if (data && data.data && data.data.id != null) { - id = data.data.id; - } - return id; - }, - source: 'digitru.st', - atype: 1 - }, - // criteo 'criteoId': { source: 'criteo.com', diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 60f450e9328..f42b7e61a52 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -63,14 +63,6 @@ userIdAsEids = [ }] }, - { - source: 'digitru.st', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }, - { source: 'criteo.com', uids: [{ diff --git a/modules/userId/index.js b/modules/userId/index.js index 1c5768edae1..afdd93a57ba 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -124,6 +124,10 @@ const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const DEFAULT_SYNC_DELAY = 500; const NO_AUCTION_DELAY = 0; +const CONSENT_DATA_COOKIE_STORAGE_CONFIG = { + name: '_pbjs_userid_consent_data', + expires: 30 // 30 days expiration, which should match how often consent is refreshed by CMPs +}; export const coreStorage = getCoreStorageManager('userid'); /** @type {string[]} */ @@ -159,17 +163,23 @@ export function setSubmoduleRegistry(submodules) { } /** - * @param {SubmoduleStorage} storage + * @param {SubmoduleContainer} submodule * @param {(Object|string)} value */ -function setStoredValue(storage, value) { +export function setStoredValue(submodule, value) { + /** + * @type {SubmoduleStorage} + */ + const storage = submodule.config.storage; + const domainOverride = (typeof submodule.submodule.domainOverride === 'function') ? submodule.submodule.domainOverride() : null; + try { const valueStr = utils.isPlainObject(value) ? JSON.stringify(value) : value; const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); if (storage.type === COOKIE) { - coreStorage.setCookie(storage.name, valueStr, expiresStr, 'Lax'); + coreStorage.setCookie(storage.name, valueStr, expiresStr, 'Lax', domainOverride); if (typeof storage.refreshInSeconds === 'number') { - coreStorage.setCookie(`${storage.name}_last`, new Date().toUTCString(), expiresStr); + coreStorage.setCookie(`${storage.name}_last`, new Date().toUTCString(), expiresStr, 'Lax', domainOverride); } } else if (storage.type === LOCAL_STORAGE) { coreStorage.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); @@ -215,6 +225,72 @@ function getStoredValue(storage, key = undefined) { return storedValue; } +/** + * makes an object that can be stored with only the keys we need to check. + * excluding the vendorConsents object since the consentString is enough to know + * if consent has changed without needing to have all the details in an object + * @param consentData + * @returns {{apiVersion: number, gdprApplies: boolean, consentString: string}} + */ +function makeStoredConsentDataObject(consentData) { + const storedConsentData = { + consentString: '', + gdprApplies: false, + apiVersion: 0 + }; + + if (consentData) { + storedConsentData.consentString = consentData.consentString; + storedConsentData.gdprApplies = consentData.gdprApplies; + storedConsentData.apiVersion = consentData.apiVersion; + } + + return storedConsentData; +} + +/** + * puts the current consent data into cookie storage + * @param consentData + */ +export function setStoredConsentData(consentData) { + try { + const expiresStr = (new Date(Date.now() + (CONSENT_DATA_COOKIE_STORAGE_CONFIG.expires * (60 * 60 * 24 * 1000)))).toUTCString(); + coreStorage.setCookie(CONSENT_DATA_COOKIE_STORAGE_CONFIG.name, JSON.stringify(makeStoredConsentDataObject(consentData)), expiresStr, 'Lax'); + } catch (error) { + utils.logError(error); + } +} + +/** + * get the stored consent data from local storage, if any + * @returns {string} + */ +function getStoredConsentData() { + let storedValue; + try { + storedValue = JSON.parse(coreStorage.getCookie(CONSENT_DATA_COOKIE_STORAGE_CONFIG.name)); + } catch (e) { + utils.logError(e); + } + return storedValue; +} + +/** + * test if the consent object stored locally matches the current consent data. + * if there is nothing in storage, return true and we'll do an actual comparison next time. + * this way, we don't force a refresh for every user when this code rolls out + * @param storedConsentData + * @param consentData + * @returns {boolean} + */ +function storedConsentDataMatchesConsentData(storedConsentData, consentData) { + return ( + typeof storedConsentData === 'undefined' || + storedConsentData === null || + utils.deepEqual(storedConsentData, makeStoredConsentDataObject(consentData)) + ); +} + /** * test if consent module is present, applies, and is valid for local storage or cookies (purpose 1) * @param {ConsentData} consentData @@ -246,7 +322,7 @@ function processSubmoduleCallbacks(submodules, cb) { // if valid, id data should be saved to cookie/html storage if (idObj) { if (submodule.config.storage) { - setStoredValue(submodule.config.storage, idObj); + setStoredValue(submodule, idObj); } // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.submodule.decode(idObj); @@ -302,7 +378,7 @@ function addIdDataToAdUnitBids(adUnits, submodules) { } /** - * This is a common function that will initalize subModules if not already done and it will also execute subModule callbacks + * 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; @@ -405,6 +481,10 @@ export const validateGdprEnforcement = hook('sync', function (submodules, consen * @returns {SubmoduleContainer[]} initialized submodules */ function initSubmodules(submodules, consentData) { + // we always want the latest consentData stored, even if we don't execute any submodules + const storedConsentData = getStoredConsentData(); + setStoredConsentData(consentData); + // gdpr consent with purpose one is required, otherwise exit immediately let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); if (!hasValidated && !hasGDPRConsent(consentData)) { @@ -426,8 +506,8 @@ function initSubmodules(submodules, consentData) { refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000); } - if (!storedId || refreshNeeded) { - // No previously saved id. Request one from submodule. + if (!storedId || refreshNeeded || !storedConsentDataMatchesConsentData(storedConsentData, consentData)) { + // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. response = submodule.submodule.getId(submodule.config.params, consentData, storedId); } else if (typeof submodule.submodule.extendId === 'function') { // If the id exists already, give submodule a chance to decide additional actions that need to be taken @@ -437,7 +517,7 @@ function initSubmodules(submodules, consentData) { if (utils.isPlainObject(response)) { if (response.id) { // A getId/extendId result assumed to be valid user id data, which should be saved to users local storage or cookies - setStoredValue(submodule.config.storage, response.id); + setStoredValue(submodule, response.id); storedId = response.id; } @@ -563,15 +643,15 @@ export function init(config) { utils.logInfo(`${MODULE_NAME} - opt-out cookie found, exit module`); return; } - // _pubcid_optout is checked for compatiblility with pubCommonId + // _pubcid_optout is checked for compatibility with pubCommonId if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && (coreStorage.getDataFromLocalStorage('_pbjs_id_optout') || coreStorage.getDataFromLocalStorage('_pubcid_optout'))) { utils.logInfo(`${MODULE_NAME} - opt-out localStorage found, exit module`); return; } // listen for config userSyncs to be set config.getConfig(conf => { - // Note: support for both 'userSync' and 'usersync' will be deprecated with Prebid.js 3.0 - const userSync = conf.userSync || conf.usersync; + // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 + const userSync = conf.userSync; if (userSync && userSync.userIds) { configRegistry = userSync.userIds; syncDelay = utils.isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 566d719c90d..a47ecd9f08c 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -25,8 +25,8 @@ pbjs.setConfig({ }, { name: "id5Id", params: { - partner: 173, //Set your real ID5 partner ID here for production, please ask for one at https://id5.io/universal-id - pd: "some-pd-string" // See https://console.id5.io/docs/public/prebid for details + partner: 173, // Set your real ID5 partner ID here for production, please ask for one at https://id5.io/universal-id + pd: "some-pd-string" // See https://wiki.id5.io/display/PD/Prebid.js+UserId+Module for details }, storage: { type: "cookie", @@ -80,7 +80,7 @@ pbjs.setConfig({ Example showing `localStorage` for user id data for some submodules ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: "unifiedId", params: { @@ -138,7 +138,7 @@ pbjs.setConfig({ Example showing how to configure a `value` object to pass directly to bid adapters ``` pbjs.setConfig({ - usersync: { + userSync: { userIds: [{ name: "pubCommonId", value: { diff --git a/modules/userIdTargeting.md b/modules/userIdTargeting.md index f99fd5308b3..340c1b6abf2 100644 --- a/modules/userIdTargeting.md +++ b/modules/userIdTargeting.md @@ -8,7 +8,7 @@ pbjs.setConfig({ // your existing userIds config - usersync: { + userSync: { userIds: [{...}, ...] }, diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 3eea270dc8d..382833fbca9 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,12 +1,17 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +const GLVID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; const BIDDER_CODE = 'vidazoo'; const BIDDER_VERSION = '1.0.0'; const CURRENCY = 'USD'; const TTL_SECONDS = 60 * 5; +const DEAL_ID_EXPIRY = 1000 * 60 * 15; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +const SESSION_ID_KEY = 'vidSid'; const INTERNAL_SYNC_TYPE = { IFRAME: 'iframe', IMAGE: 'img' @@ -23,25 +28,43 @@ export const SUPPORTED_ID_SYSTEMS = { 'idl_env': 1, 'lipb': 1, 'netId': 1, - 'parrableid': 1, + 'parrableId': 1, 'pubcid': 1, 'tdid': 1, }; +const storage = getStorageManager(GLVID); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.cootlogix.com`; } +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + function isBidRequestValid(bid) { const params = bid.params || {}; - return !!(params.cId && params.pId); + return !!(extractCID(params) && extractPID(params)); } function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { const { params, bidId, userId, adUnitCode } = bid; - const { bidFloor, cId, pId, ext, subDomain } = params; + const { bidFloor, ext } = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const sId = getVidazooSessionId(); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); let data = { url: encodeURIComponent(topWindowUrl), @@ -50,8 +73,10 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { bidId: bidId, adUnitCode: adUnitCode, publisherId: pId, + sessionId: sId, sizes: sizes, dealId: dealId, + uniqueDealId: uniqueDealId, bidderVersion: BIDDER_VERSION, prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}` @@ -70,6 +95,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { if (bidderRequest.uspConsent) { data.usPrivacy = bidderRequest.uspConsent } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -96,6 +122,9 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { case 'lipb': payloadRef[key] = userId.lipbid; break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; default: payloadRef[key] = userId; } @@ -182,7 +211,7 @@ function getUserSyncs(syncOptions, responses) { return []; } -function hashCode(s, prefix = '_') { +export function hashCode(s, prefix = '_') { const l = s.length; let h = 0 let i = 0; @@ -192,35 +221,69 @@ function hashCode(s, prefix = '_') { return prefix + h; } -function getNextDealId(key) { +export function getNextDealId(key, expiry = DEAL_ID_EXPIRY) { try { - const currentValue = Number(getStorageItem(key) || 0); + const data = getStorageItem(key); + let currentValue = 0; + let timestamp; + + if (data && data.value && Date.now() - data.created < expiry) { + currentValue = data.value; + timestamp = data.created; + } + const nextValue = currentValue + 1; - setStorageItem(key, nextValue); + setStorageItem(key, nextValue, timestamp); return nextValue; } catch (e) { return 0; } } -function getStorage() { - return window['sessionStorage']; +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getVidazooSessionId() { + return getStorageItem(SESSION_ID_KEY) || ''; } -function getStorageItem(key) { +export function getStorageItem(key) { try { - return getStorage().getItem(key); - } catch (e) { - return null; - } + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { } + + return null; } -function setStorageItem(key, value) { +export function setStorageItem(key, value, timestamp) { try { - getStorage().setItem(key, String(value)); + const created = timestamp || Date.now(); + const data = JSON.stringify({ value, created }); + storage.setDataInLocalStorage(key, data); } catch (e) { } } +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + export const spec = { code: BIDDER_CODE, version: BIDDER_VERSION, diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index 255bf24098b..b9114d4f1bf 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -62,7 +62,6 @@ function buildBidRequests(validBidRequests) { const item = { bidId: validBidRequest.bidId, - placementId: params.placementId, bidfloor: parseFloat(params.bidfloor) || 0, position: parseInt(params.position) || 1, instl: parseInt(params.instl) || 0, @@ -170,7 +169,7 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], - isBidRequestValid: (bid) => Boolean(bid.bidId && bid.params && +bid.params.placementId && +bid.params.pubId), + isBidRequestValid: (bid) => Boolean(bid.bidId && bid.params && +bid.params.zoneId), /** * @param {Object[]} validBidRequests - array of valid bid requests @@ -181,12 +180,12 @@ export const spec = { const payload = getCommonBidsData(bidderRequest); payload.bidRequests = buildBidRequests(validBidRequests); - let pubId = ''; - if (validBidRequests[0] && validBidRequests[0].params && +validBidRequests[0].params.pubId) { - pubId = +validBidRequests[0].params.pubId; + let zoneId = ''; + if (validBidRequests[0] && validBidRequests[0].params && +validBidRequests[0].params.zoneId) { + zoneId = +validBidRequests[0].params.zoneId; } - const url = `${ENDPOINT}?pubId=${pubId}`; + const url = `${ENDPOINT}?pubId=${zoneId}`; return { method: 'POST', diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 9c2b6abf475..b2a9176e342 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -37,7 +37,7 @@ export const spec = { utils._each(validBidRequests, function (bid) { adslotIds.push(bid.params.adslotId) if (bid.params.targeting) { - query.t = createQueryString(bid.params.targeting) + query.t = createTargetingString(bid.params.targeting) } if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { query.ids = createUserIdString(bid.userIdAsEids) @@ -49,10 +49,16 @@ export const spec = { } }) - if (bidderRequest && bidderRequest.gdprConsent) { - query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + query.pubref = bidderRequest.refererInfo.referer + } + + if (bidderRequest.gdprConsent) { + query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + if (query.gdpr) { + query.consent = bidderRequest.gdprConsent.consentString + } } } @@ -198,6 +204,23 @@ function createQueryString (obj) { return str.join('&') } +/** + * Creates an unencoded targeting string out of an object with key-values + * @param {Object} obj + * @returns {String} + */ +function createTargetingString (obj) { + let str = [] + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + let key = p + let val = obj[p] + str.push(key + '=' + val) + } + } + return str.join('&') +} + /** * Handles an outstream response after the library is loaded * @param {Object} bid diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 55495979ea4..829b573ffd9 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -16,7 +16,7 @@ export const spec = { * @param {object} bid, bid to validate * @return boolean, true if valid, otherwise false */ - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return !!(bid && bid.adUnitCode && bid.bidId); }, /** @@ -25,7 +25,7 @@ export const spec = { * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(bidRequests, bidderRequest) { + buildRequests: function (bidRequests, bidderRequest) { let serverRequest = { p: [], page_url: bidderRequest.refererInfo.referer, @@ -33,29 +33,23 @@ export const spec = { pr: bidderRequest.refererInfo.referer, scrd: localWindow.devicePixelRatio || 0, dnt: getDNT(), - e: getEnvironment(), description: getPageDescription(), title: localWindow.document.title || '', w: localWindow.innerWidth, h: localWindow.innerHeight, - userConsent: - JSON.stringify({ - // case of undefined, stringify will remove param - gdprApplies: - bidderRequest && bidderRequest.gdprConsent - ? bidderRequest.gdprConsent.gdprApplies - : '', - cmp: - bidderRequest && bidderRequest.gdprConsent - ? bidderRequest.gdprConsent.consentString - : '', - }), - us_privacy: - bidderRequest && bidderRequest.uspConsent - ? bidderRequest.uspConsent - : '', + userConsent: JSON.stringify({ + // case of undefined, stringify will remove param + gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', + cmp: utils.deepAccess(bidderRequest, 'gdprConsent.consentString') || '' + }), + us_privacy: utils.deepAccess(bidderRequest, 'uspConsent') || '' }; + const mtp = window.navigator.maxTouchPoints; + if (mtp) { + serverRequest.mtp = mtp; + } + bidRequests.forEach(request => { serverRequest.p.push(addPlacement(request)); const pubcid = getId(request, 'pubcid'); @@ -91,7 +85,7 @@ export const spec = { * @param serverResponse successful response from Ad Server * @return {Bid[]} an array of bids */ - interpretResponse: function(serverResponse) { + interpretResponse: function (serverResponse) { let bids = []; let data = serverResponse.body; if (data.length > 0) { @@ -103,7 +97,7 @@ export const spec = { } return bids; }, - getUserSyncs: function() { + getUserSyncs: function () { return []; } }; @@ -175,154 +169,6 @@ function getPageDescription() { } } -/*************************************** - * Detect Environment Helper Functions - ***************************************/ - -/** - * Represents a method for loading Yieldmo ads. Environments affect - * which formats can be loaded into the page - * Environments: - * CodeOnPage: 0, // div directly on publisher's page - * Amp: 1, // google Accelerate Mobile Pages ampproject.org - * Mraid = 2, // native loaded through the MRAID spec, without Yieldmo's SDK https://www.iab.net/media/file/IAB_MRAID_v2_FINAL.pdf - * Dfp: 4, // google doubleclick for publishers https://www.doubleclickbygoogle.com/ - * DfpInAmp: 5, // AMP page containing a DFP iframe - * SafeFrame: 10, - * DfpSafeFrame: 11,Sandboxed: 16, // An iframe that can't get to the top window. - * SuperSandboxed: 89, // An iframe without allow-same-origin - * Unknown: 90, // A default sandboxed implementation delivered by EnvironmentDispatch when all positive environment checks fail - */ - -/** - * Detects what environment we're in - * @returns Environment kind - */ -function getEnvironment() { - if (isSuperSandboxedIframe()) { - return 89; - } else if (isDfpInAmp()) { - return 5; - } else if (isDfp()) { - return 4; - } else if (isAmp()) { - return 1; - } else if (isDFPSafeFrame()) { - return 11; - } else if (isSafeFrame()) { - return 10; - } else if (isMraid()) { - return 2; - } else if (isCodeOnPage()) { - return 0; - } else if (isSandboxedIframe()) { - return 16; - } else { - return 90; - } -} - -/** - * @returns true if we are running on the top window at dispatch time - */ -function isCodeOnPage() { - return window === window.parent; -} - -/** - * @returns true if the environment is both DFP and AMP - */ -function isDfpInAmp() { - return isDfp() && isAmp(); -} - -/** - * @returns true if the window is in an iframe whose id and parent element id match DFP - */ -function isDfp() { - try { - const frameElement = window.frameElement; - const parentElement = window.frameElement.parentNode; - if (frameElement && parentElement) { - return ( - frameElement.id.indexOf('google_ads_iframe') > -1 && - parentElement.id.indexOf('google_ads_iframe') > -1 - ); - } - return false; - } catch (e) { - return false; - } -} - -/** - * @returns true if there is an AMP context object - */ -function isAmp() { - try { - const ampContext = window.context || window.parent.context; - if (ampContext && ampContext.pageViewId) { - return ampContext; - } - return false; - } catch (e) { - return false; - } -} - -/** - * @returns true if the environment is a SafeFrame. - */ -function isSafeFrame() { - return window.$sf && window.$sf.ext; -} - -/** - * @returns true if the environment is a dfp safe frame. - */ -function isDFPSafeFrame() { - if (window.location && window.location.href) { - const href = window.location.href; - return ( - isSafeFrame() && - href.indexOf('google') !== -1 && - href.indexOf('safeframe') !== -1 - ); - } - return false; -} - -/** - * Return true if we are in an iframe and can't access the top window. - */ -function isSandboxedIframe() { - return window.top !== window && !window.frameElement; -} - -/** - * Return true if we cannot document.write to a child iframe (this implies no allow-same-origin) - */ -function isSuperSandboxedIframe() { - const sacrificialIframe = window.document.createElement('iframe'); - try { - sacrificialIframe.setAttribute('style', 'display:none'); - window.document.body.appendChild(sacrificialIframe); - sacrificialIframe.contentWindow._testVar = true; - window.document.body.removeChild(sacrificialIframe); - return false; - } catch (e) { - window.document.body.removeChild(sacrificialIframe); - return true; - } -} - -/** - * @returns true if the window has the attribute identifying MRAID - */ -function isMraid() { - return !!window.mraid; -} - /** * Gets an id from the userId object if it exists * @param {*} request @@ -330,14 +176,5 @@ function isMraid() { * @returns an id if there is one, or undefined */ function getId(request, idType) { - let id; - if ( - request && - request.userId && - request.userId[idType] && - typeof request.userId === 'object' - ) { - id = request.userId[idType]; - } - return id; + return (typeof utils.deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined; } diff --git a/modules/yieldoneAnalyticsAdapter.js b/modules/yieldoneAnalyticsAdapter.js index dd40e205b07..542c0917708 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.js @@ -123,7 +123,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { auctionManager.getAdUnitCodes(), auctionManager.getBidsReceived() ); - if (yieldoneAnalytics.eventsStorage[currentAuctionId]) { + if (yieldoneAnalytics.eventsStorage[currentAuctionId] && yieldoneAnalytics.eventsStorage[currentAuctionId].events.length) { yieldoneAnalytics.eventsStorage[currentAuctionId].page = {url: referrers[currentAuctionId]}; yieldoneAnalytics.eventsStorage[currentAuctionId].pubId = pubId; yieldoneAnalytics.eventsStorage[currentAuctionId].wrapper_version = '$prebid.version$'; @@ -139,8 +139,8 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { } } }, - sendStat(events, auctionId) { - if (!events) return; + sendStat(data, auctionId) { + if (!data || !data.events || !data.events.length) return; delete yieldoneAnalytics.eventsStorage[auctionId]; ajax( url, @@ -148,7 +148,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { success: function() {}, error: function() {} }, - JSON.stringify(events), + JSON.stringify(data), { method: 'POST' } diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 59240878426..574967db291 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -8,6 +8,7 @@ const BIDDER_CODE = 'yieldone'; const ENDPOINT_URL = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js'; +const CMER_PLAYER_URL = 'https://an.cmertv.com/hb/renderer/cmertv-video-yone-prebid.min.js'; const VIEWABLE_PERCENTAGE_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/prebid-adformat-config.js'; export const spec = { @@ -136,7 +137,11 @@ export const spec = { } else if (response.adm) { bidResponse.mediaType = VIDEO; bidResponse.vastXml = response.adm; - bidResponse.renderer = newRenderer(response); + if (renderId === 'cmer') { + bidResponse.renderer = newCmerRenderer(response); + } else { + bidResponse.renderer = newRenderer(response); + } } bidResponses.push(bidResponse); @@ -175,4 +180,26 @@ function outstreamRender(bid) { }); } +function newCmerRenderer(response) { + const renderer = Renderer.install({ + id: response.uid, + url: CMER_PLAYER_URL, + loaded: false, + }); + + try { + renderer.setRender(cmerRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on newRenderer', err); + } + + return renderer; +} + +function cmerRender(bid) { + bid.renderer.push(() => { + window.CMERYONEPREBID.renderPrebid(bid); + }); +} + registerBidder(spec); diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index b346a26c843..3c27ca9754a 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -3,121 +3,70 @@ import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import * as utils from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; -const emptyUrl = ''; -const analyticsType = 'endpoint'; -const yuktamediaAnalyticsVersion = 'v2.0.0'; +const storage = getStorageManager(); +const yuktamediaAnalyticsVersion = 'v3.0.0'; let initOptions; let auctionTimestamp; -let events = { - bids: [] + +const events = { + auctions: {} }; +const localStoragePrefix = 'yuktamediaAnalytics_'; +const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; -var yuktamediaAnalyticsAdapter = Object.assign(adapter( - { - emptyUrl, - analyticsType - }), { - track({ eventType, args }) { - if (typeof args !== 'undefined') { - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { - args.forEach(item => { mapBidResponse(item, 'timeout'); }); - } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { - events.auctionInit = args; - auctionTimestamp = args.timestamp; - } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { - mapBidRequests(args).forEach(item => { events.bids.push(item) }); - } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { - mapBidResponse(args, 'response'); - } else if (eventType === CONSTANTS.EVENTS.BID_WON) { - send({ - bidWon: mapBidResponse(args, 'win') - }, 'won'); - } +function getParameterByName(param) { + let vars = {}; + window.location.href.replace(location.hash, '').replace( + /[?&]+([^=&]+)=?([^&]*)?/gi, + function (m, key, value) { + vars[key] = value !== undefined ? value : ''; } + ); + return vars[param] ? vars[param] : ''; +} - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - send(events, 'auctionEnd'); - } - } -}); +function isNavigatorSendBeaconSupported() { + return ('navigator' in window) && ('sendBeacon' in window.navigator); +} -function mapBidRequests(params) { - let arr = []; - if (typeof params.bids !== 'undefined' && params.bids.length) { - params.bids.forEach(function (bid) { - arr.push({ - bidderCode: bid.bidder, - bidId: bid.bidId, - adUnitCode: bid.adUnitCode, - requestId: bid.bidderRequestId, - auctionId: bid.auctionId, - transactionId: bid.transactionId, - sizes: utils.parseSizesInput(bid.mediaTypes.banner.sizes).toString(), - renderStatus: 1, - requestTimestamp: params.auctionStart - }); - }); +function updateSessionId() { + if (isSessionIdTimeoutExpired()) { + let newSessionId = utils.generateUUID(); + storage.setDataInLocalStorage(localStoragePrefix.concat('session_id'), newSessionId); } - return arr; + initOptions.sessionId = getSessionId(); + updateSessionIdTimeout(); } -function mapBidResponse(bidResponse, status) { - if (status !== 'win') { - let bid = events.bids.filter(o => o.bidId == bidResponse.bidId || o.bidId == bidResponse.requestId)[0]; - Object.assign(bid, { - bidderCode: bidResponse.bidder, - bidId: status == 'timeout' ? bidResponse.bidId : bidResponse.requestId, - adUnitCode: bidResponse.adUnitCode, - auctionId: bidResponse.auctionId, - creativeId: bidResponse.creativeId, - transactionId: bidResponse.transactionId, - currency: bidResponse.currency, - cpm: bidResponse.cpm, - netRevenue: bidResponse.netRevenue, - mediaType: bidResponse.mediaType, - statusMessage: bidResponse.statusMessage, - status: bidResponse.status, - renderStatus: status == 'timeout' ? 3 : 2, - timeToRespond: bidResponse.timeToRespond, - requestTimestamp: bidResponse.requestTimestamp, - responseTimestamp: bidResponse.responseTimestamp - }); - } else if (status == 'win') { - return { - bidderCode: bidResponse.bidder, - bidId: bidResponse.requestId, - adUnitCode: bidResponse.adUnitCode, - auctionId: bidResponse.auctionId, - creativeId: bidResponse.creativeId, - transactionId: bidResponse.transactionId, - currency: bidResponse.currency, - cpm: bidResponse.cpm, - netRevenue: bidResponse.netRevenue, - renderedSize: bidResponse.size, - mediaType: bidResponse.mediaType, - statusMessage: bidResponse.statusMessage, - status: bidResponse.status, - renderStatus: 4, - timeToRespond: bidResponse.timeToRespond, - requestTimestamp: bidResponse.requestTimestamp, - responseTimestamp: bidResponse.responseTimestamp - } - } +function updateSessionIdTimeout() { + storage.setDataInLocalStorage(localStoragePrefix.concat('session_timeout'), Date.now()); +} + +function isSessionIdTimeoutExpired() { + let cpmSessionTimestamp = storage.getDataFromLocalStorage(localStoragePrefix.concat('session_timeout')); + return Date.now() - cpmSessionTimestamp > 3600000; +} + +function getSessionId() { + return storage.getDataFromLocalStorage(localStoragePrefix.concat('session_id')) ? storage.getDataFromLocalStorage(localStoragePrefix.concat('session_id')) : ''; +} + +function isUtmTimeoutExpired() { + let utmTimestamp = storage.getDataFromLocalStorage(localStoragePrefix.concat('utm_timeout')); + return (Date.now() - utmTimestamp) > 3600000; } function send(data, status) { - let location = utils.getWindowLocation(); - if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - data.auctionInit = Object.assign({ host: location.host, path: location.pathname, search: location.search }, data.auctionInit); - } - data.initOptions = initOptions; + const location = utils.getWindowLocation(); + data.initOptions = Object.assign({ host: location.host, path: location.pathname, search: location.search }, initOptions); - let yuktamediaAnalyticsRequestUrl = utils.buildUrl({ + const yuktamediaAnalyticsRequestUrl = utils.buildUrl({ protocol: 'https', hostname: 'analytics-prebid.yuktamedia.com', - pathname: status == 'auctionEnd' ? '/api/bids' : '/api/bid/won', + pathname: '/api/bids', search: { auctionTimestamp: auctionTimestamp, yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, @@ -125,12 +74,176 @@ function send(data, status) { } }); - ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); + if (isNavigatorSendBeaconSupported()) { + window.navigator.sendBeacon(yuktamediaAnalyticsRequestUrl, JSON.stringify(data)); + } else { + ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); + } } +var yuktamediaAnalyticsAdapter = Object.assign(adapter({analyticsType: 'endpoint'}), { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + utils.logInfo(localStoragePrefix + 'AUCTION_INIT:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + events.auctions[args.auctionId] = { bids: {} }; + auctionTimestamp = args.timestamp; + } + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + utils.logInfo(localStoragePrefix + 'BID_REQUESTED:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } + events.auctions[args.auctionId]['timeStamp'] = args.start; + args.bids.forEach(function (bidRequest) { + events.auctions[args.auctionId]['bids'][bidRequest.bidId] = { + bidder: bidRequest.bidder, + adUnit: bidRequest.adUnitCode, + sizes: utils.parseSizesInput(bidRequest.sizes).toString(), + isBid: false, + won: false, + timeout: false, + renderStatus: 'bid-requested', + bidId: bidRequest.bidId, + auctionId: args.auctionId + } + if (typeof initOptions.enableUserIdCollection !== 'undefined' && initOptions.enableUserIdCollection && typeof bidRequest['userId'] !== 'undefined') { + for (let [userIdProvider, userId] in Object.entries(bidRequest['userId'])) { + userIdProvider = typeof userIdProvider !== 'string' ? JSON.stringify(userIdProvider) : userIdProvider; + userId = typeof userId !== 'string' ? JSON.stringify(userId) : userId; + events.auctions[args.auctionId]['bids'][bidRequest.bidId]['userID-'.concat(userIdProvider)] = userId; + } + } + }); + } + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + utils.logInfo(localStoragePrefix + 'BID_RESPONSE:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + let bidResponse = events.auctions[args.auctionId]['bids'][args.requestId]; + bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD; + bidResponse.cpm = args.cpm; + bidResponse.currency = args.currency; + bidResponse.netRevenue = args.netRevenue; + bidResponse.dealId = typeof args.dealId !== 'undefined' ? args.dealId : ''; + bidResponse.mediaType = args.mediaType; + if (bidResponse.mediaType === 'native') { + bidResponse.nativeTitle = typeof args['native']['title'] !== 'undefined' ? args['native']['title'] : ''; + bidResponse.nativeSponsoredBy = typeof args['native']['sponsoredBy'] !== 'undefined' ? args['native']['sponsoredBy'] : ''; + } + bidResponse.timeToRespond = args.timeToRespond; + bidResponse.requestTimestamp = args.requestTimestamp; + bidResponse.responseTimestamp = args.responseTimestamp; + bidResponse.bidForSize = args.size; + for (const [adserverTargetingKey, adserverTargetingValue] of Object.entries(args.adserverTargeting)) { + if (['body', 'icon', 'image', 'linkurl', 'host', 'path'].every((ele) => !adserverTargetingKey.includes(ele))) { + bidResponse['adserverTargeting-' + adserverTargetingKey] = adserverTargetingValue; + } + } + bidResponse.renderStatus = 'bid-response-received'; + } + } + break; + case CONSTANTS.EVENTS.NO_BID: + utils.logInfo(localStoragePrefix + 'NO_BID:', JSON.stringify(args)); + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const noBid = events.auctions[args.auctionId]['bids'][args.bidId]; + noBid.renderStatus = 'no-bid'; + } + } + break; + case CONSTANTS.EVENTS.BID_WON: + utils.logInfo(localStoragePrefix + 'BID_WON:', JSON.stringify(args)); + if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { + updateSessionId(); + } + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const wonBid = events.auctions[args.auctionId]['bids'][args.requestId]; + wonBid.won = true; + wonBid.renderStatus = 'bid-won'; + send({ 'bids': [wonBid] }, 'won'); + } + } + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + utils.logInfo(localStoragePrefix + 'BID_TIMEOUT:', JSON.stringify(args)); + if (args.length) { + args.forEach(timeout => { + if (typeof timeout !== 'undefined' && typeof timeout.auctionId !== 'undefined' && timeout.auctionId.length) { + if (typeof events.auctions[args.auctionId] === 'undefined') { + events.auctions[args.auctionId] = { bids: {} }; + } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { + const timeoutBid = events.auctions[timeout.auctionId].bids[timeout.bidId]; + timeoutBid.timeout = true; + timeoutBid.renderStatus = 'bid-timedout'; + } + } + }); + } + break; + case CONSTANTS.EVENTS.AUCTION_END: + utils.logInfo(localStoragePrefix + 'AUCTION_END:', JSON.stringify(args)); + if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { + updateSessionId(); + } + if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { + const bids = Object.values(events.auctions[args.auctionId]['bids']); + send({ 'bids': bids }, 'auctionEnd'); + } + break; + } + } + } +}); + +yuktamediaAnalyticsAdapter.buildUtmTagData = function (options) { + let utmTagData = {}; + let utmTagsDetected = false; + if (typeof options.enableUTMCollection !== 'undefined' && options.enableUTMCollection) { + utmTags.forEach(function (utmTagKey) { + let utmTagValue = getParameterByName(utmTagKey); + if (utmTagValue !== '') { + utmTagsDetected = true; + } + utmTagData[utmTagKey] = utmTagValue; + }); + utmTags.forEach(function (utmTagKey) { + if (utmTagsDetected) { + storage.setDataInLocalStorage(localStoragePrefix.concat(utmTagKey), utmTagData[utmTagKey]); + storage.setDataInLocalStorage(localStoragePrefix.concat('utm_timeout'), Date.now()); + } else { + if (!isUtmTimeoutExpired()) { + utmTagData[utmTagKey] = storage.getDataFromLocalStorage(localStoragePrefix.concat(utmTagKey)) ? storage.getDataFromLocalStorage(localStoragePrefix.concat(utmTagKey)) : ''; + storage.setDataInLocalStorage(localStoragePrefix.concat('utm_timeout'), Date.now()); + } + } + }); + } + return utmTagData; +}; + yuktamediaAnalyticsAdapter.originEnableAnalytics = yuktamediaAnalyticsAdapter.enableAnalytics; yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { - initOptions = config.options; + if (config && config.options) { + if (typeof config.options.pubId === 'undefined' || typeof config.options.pubKey === 'undefined') { + utils.logError('Need pubId and pubKey to log auction results. Please contact a YuktaMedia representative if you do not know your pubId and pubKey.'); + return; + } + } + initOptions = Object.assign({}, config.options, this.buildUtmTagData(config.options)); yuktamediaAnalyticsAdapter.originEnableAnalytics(config); }; diff --git a/modules/yuktamediaAnalyticsAdapter.md b/modules/yuktamediaAnalyticsAdapter.md index a21675b6b1d..af47985c834 100644 --- a/modules/yuktamediaAnalyticsAdapter.md +++ b/modules/yuktamediaAnalyticsAdapter.md @@ -1,5 +1,5 @@ # Overview -Module Name: YuktaMedia Analytics Adapter +Module Name: YuktaOne Analytics by YuktaMedia Module Type: Analytics Adapter @@ -15,8 +15,11 @@ Analytics adapter for prebid provided by YuktaMedia. Contact info@yuktamedia.com { provider: 'yuktamedia', options : { - pubId : 50357 //id provided by YuktaMedia LLP - pubKey: 'xxx' //key provided by YuktaMedia LLP + pubId : 50357, // id provided by YuktaMedia LLP + pubKey: 'xxx', // key provided by YuktaMedia LLP + enableUTMCollection: true, // set true if want to collect utm info + enableSession: true, // set true if want to collect information by sessions + enableUserIdCollection: true // set true if want to collect user ID module info } } ``` diff --git a/package-lock.json b/package-lock.json index 2fc53a75352..4f3d2120d72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "prebid.js", - "version": "3.24.0-pre", + "version": "3.27.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { - "@babel/highlight": "^7.10.1" + "@babel/highlight": "^7.10.4" } }, "@babel/compat-data": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.1.tgz", - "integrity": "sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz", + "integrity": "sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw==", "dev": true, "requires": { "browserslist": "^4.12.0", @@ -25,24 +25,24 @@ } }, "@babel/core": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.2.tgz", - "integrity": "sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.2", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helpers": "^7.10.1", - "@babel/parser": "^7.10.2", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.2", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", + "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.5", + "@babel/types": "^7.10.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" @@ -66,43 +66,42 @@ } }, "@babel/generator": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", - "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", "dev": true, "requires": { - "@babel/types": "^7.10.2", + "@babel/types": "^7.10.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", - "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz", - "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-compilation-targets": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz", - "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.10.1", + "@babel/compat-data": "^7.10.4", "browserslist": "^4.12.0", "invariant": "^2.2.4", "levenary": "^1.1.1", @@ -110,337 +109,337 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz", - "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz", - "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-regex": "^7.10.1", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", "regexpu-core": "^4.7.0" } }, "@babel/helper-define-map": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz", - "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz", - "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", + "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", "dev": true, "requires": { - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-hoist-variables": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz", - "integrity": "sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz", - "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.5" } }, "@babel/helper-module-imports": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz", - "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-module-transforms": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz", - "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", - "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-plugin-utils": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz", - "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, "@babel/helper-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz", - "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "dev": true, "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz", - "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-wrap-function": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-replace-supers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", - "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-simple-access": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz", - "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", "dev": true, "requires": { - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", "dev": true }, "@babel/helper-wrap-function": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz", - "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", - "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", "dev": true, "requires": { - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz", - "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz", - "integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz", - "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz", - "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz", - "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz", - "integrity": "sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-numeric-separator": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz", - "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", + "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.1" + "@babel/plugin-transform-parameters": "^7.10.4" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz", - "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz", - "integrity": "sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", + "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz", - "integrity": "sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz", - "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -453,12 +452,12 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz", - "integrity": "sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-dynamic-import": { @@ -489,12 +488,12 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz", - "integrity": "sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -525,394 +524,393 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz", - "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz", - "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz", - "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz", - "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz", - "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz", + "integrity": "sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz", - "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-define-map": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz", - "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz", - "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz", - "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz", - "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz", - "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", - "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz", - "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz", - "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz", - "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz", - "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz", - "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz", - "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.10.1", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz", - "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4" } }, "@babel/plugin-transform-new-target": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz", - "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz", - "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" } }, "@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz", - "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz", - "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz", - "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz", - "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz", - "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", + "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz", - "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-regex": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz", - "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz", - "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz", - "integrity": "sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz", - "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.2.tgz", - "integrity": "sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.10.1", - "@babel/helper-compilation-targets": "^7.10.2", - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-proposal-async-generator-functions": "^7.10.1", - "@babel/plugin-proposal-class-properties": "^7.10.1", - "@babel/plugin-proposal-dynamic-import": "^7.10.1", - "@babel/plugin-proposal-json-strings": "^7.10.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", - "@babel/plugin-proposal-numeric-separator": "^7.10.1", - "@babel/plugin-proposal-object-rest-spread": "^7.10.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.1", - "@babel/plugin-proposal-optional-chaining": "^7.10.1", - "@babel/plugin-proposal-private-methods": "^7.10.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.1", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz", + "integrity": "sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.4", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.10.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.1", + "@babel/plugin-syntax-class-properties": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0", "@babel/plugin-syntax-json-strings": "^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.1", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.1", - "@babel/plugin-transform-arrow-functions": "^7.10.1", - "@babel/plugin-transform-async-to-generator": "^7.10.1", - "@babel/plugin-transform-block-scoped-functions": "^7.10.1", - "@babel/plugin-transform-block-scoping": "^7.10.1", - "@babel/plugin-transform-classes": "^7.10.1", - "@babel/plugin-transform-computed-properties": "^7.10.1", - "@babel/plugin-transform-destructuring": "^7.10.1", - "@babel/plugin-transform-dotall-regex": "^7.10.1", - "@babel/plugin-transform-duplicate-keys": "^7.10.1", - "@babel/plugin-transform-exponentiation-operator": "^7.10.1", - "@babel/plugin-transform-for-of": "^7.10.1", - "@babel/plugin-transform-function-name": "^7.10.1", - "@babel/plugin-transform-literals": "^7.10.1", - "@babel/plugin-transform-member-expression-literals": "^7.10.1", - "@babel/plugin-transform-modules-amd": "^7.10.1", - "@babel/plugin-transform-modules-commonjs": "^7.10.1", - "@babel/plugin-transform-modules-systemjs": "^7.10.1", - "@babel/plugin-transform-modules-umd": "^7.10.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.10.1", - "@babel/plugin-transform-object-super": "^7.10.1", - "@babel/plugin-transform-parameters": "^7.10.1", - "@babel/plugin-transform-property-literals": "^7.10.1", - "@babel/plugin-transform-regenerator": "^7.10.1", - "@babel/plugin-transform-reserved-words": "^7.10.1", - "@babel/plugin-transform-shorthand-properties": "^7.10.1", - "@babel/plugin-transform-spread": "^7.10.1", - "@babel/plugin-transform-sticky-regex": "^7.10.1", - "@babel/plugin-transform-template-literals": "^7.10.1", - "@babel/plugin-transform-typeof-symbol": "^7.10.1", - "@babel/plugin-transform-unicode-escapes": "^7.10.1", - "@babel/plugin-transform-unicode-regex": "^7.10.1", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.10.4", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.10.2", + "@babel/types": "^7.10.4", "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", @@ -934,48 +932,48 @@ } }, "@babel/runtime": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", - "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", + "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true } } }, "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/traverse": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", - "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" }, "dependencies": { "debug": { @@ -996,13 +994,13 @@ } }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -1287,6 +1285,12 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -1494,6 +1498,12 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -1834,15 +1844,15 @@ } }, "@jest/types": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", - "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.1.0.tgz", + "integrity": "sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" + "chalk": "^4.0.0" }, "dependencies": { "ansi-styles": { @@ -1856,9 +1866,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -1898,16 +1908,16 @@ } }, "@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.3.tgz", - "integrity": "sha512-TAdNkeGB5Fe4Og+ZkAr1Kvn9by2sfL44IAHFtxlh1BA1XJ5cLpO9iSNki5opWESv3l3vSHsZ9BNKuqFKbEbFaA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", + "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", "dev": true, "requires": { "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.1", - "loader-utils": "^1.4.0", + "istanbul-lib-instrument": "^4.0.3", + "loader-utils": "^2.0.0", "merge-source-map": "^1.1.0", - "schema-utils": "^2.6.4" + "schema-utils": "^2.7.0" } }, "@kiosked/ulid": { @@ -1916,15 +1926,15 @@ "integrity": "sha512-ZKt2KIgGHDaGfKt6FjYvCpDvBXZRRoE8b+wDOlAV76aXKpq6ITiSUnPYevR4y55NKDnwCvwOrjWe+aVOCAK8kQ==" }, "@sindresorhus/is": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", - "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", + "integrity": "sha512-kqA5I6Yun7PBHk8WN9BBP1c7FfN2SrD05GuVSEYPqDb4nerv7HqYfgBfMIKmT/EuejURkJKLZuLyGKGs6WEG9w==", "dev": true }, "@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -1966,9 +1976,9 @@ } }, "@types/babel__core": { - "version": "7.1.8", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.8.tgz", - "integrity": "sha512-KXBiQG2OXvaPWFPDS1rD8yV9vO0OuWIqAEqLsbfX0oU2REN5KuoMnZ1gClWcBhO5I3n6oTVAmrMufOvRqdmFTQ==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -1998,9 +2008,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.12.tgz", - "integrity": "sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", + "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -2031,9 +2041,9 @@ "dev": true }, "@types/istanbul-lib-coverage": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz", - "integrity": "sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", "dev": true }, "@types/istanbul-lib-report": { @@ -2056,9 +2066,15 @@ } }, "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/keyv": { @@ -2071,11 +2087,20 @@ } }, "@types/node": { - "version": "14.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.9.tgz", - "integrity": "sha512-0sCTiXKXELOBxvZLN4krQ0FPOAA7ij+6WwvD0k/PHd9/KAkr4dXel5J9fh6F4x1FwAQILqAWkmpeuS6mjf1iKA==", + "version": "14.0.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.24.tgz", + "integrity": "sha512-btt/oNOiDWcSuI721MdL8VQGnjsKjlTMdrKyTcLCKeQp/n4AAMFJ961wMbp+09y8WuGPClDEv07RIItdXKIXAA==", "dev": true }, + "@types/puppeteer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz", + "integrity": "sha512-t03eNKCvWJXhQ8wkc5C6GYuSqMEdKLOX0GLMGtks25YZr38wKZlKTwGM/BoAPVtdysX7Bb9tdwrDS1+NrW3RRA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -2128,14 +2153,14 @@ } }, "@wdio/cli": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-6.1.16.tgz", - "integrity": "sha512-q7JaEiLU2mdOibeKAQFqdWTS2evdkwgWSft1rmWDN7idiV39uncTTUcwlXBKE2a9yDk/8qn6EEXdBLthOCfyOA==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-6.3.4.tgz", + "integrity": "sha512-eXA4rR6DwhNtXx1Hxknwgl7jGt/q4ZErCB8aOX9rowEoPOxwPQStd6yJcqI2QE8+AC1S72PKC4w+0WImL+M6Bw==", "dev": true, "requires": { "@wdio/config": "6.1.14", "@wdio/logger": "6.0.16", - "@wdio/utils": "6.1.8", + "@wdio/utils": "6.3.0", "async-exit-hook": "^2.0.1", "chalk": "^4.0.0", "chokidar": "^3.0.0", @@ -2146,8 +2171,9 @@ "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", - "log-update": "^4.0.0", - "webdriverio": "6.1.16", + "mkdirp": "^1.0.4", + "recursive-readdir": "^2.2.2", + "webdriverio": "6.3.4", "yargs": "^15.0.1", "yarn-install": "^1.0.0" }, @@ -2163,9 +2189,9 @@ } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -2187,61 +2213,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -2252,9 +2229,9 @@ } }, "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -2267,18 +2244,18 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "yargs-parser": "^18.1.2" } } } }, "@wdio/concise-reporter": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-6.1.14.tgz", - "integrity": "sha512-QKGiIPE2sYJpQcQ5zogUogu+Yldkx/FUM4LC01lc3v0Nww7n0p0fJA3U4cKFAqyW0gXCwW7m6/9c5fDgaSv8+g==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-6.3.0.tgz", + "integrity": "sha512-H7yILps+dKK1k4XoVE5HOVMpTHN321SFmjjMgtq1zfiC6Dph7Unl4ODmnyVLD5Kk3ycQ31PfOBr0QPyKnLUFiQ==", "dev": true, "requires": { - "@wdio/reporter": "6.1.14", + "@wdio/reporter": "6.3.0", "chalk": "^4.0.0", "pretty-ms": "^7.0.0" }, @@ -2294,9 +2271,9 @@ } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -2347,14 +2324,14 @@ } }, "@wdio/local-runner": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-6.1.16.tgz", - "integrity": "sha512-3+pT2fcMXFAnELA6jYjVm6Nt8Il7tGL66A90UbRiT0sL2faTcD3uGAPbmzxMclsmWLh7C04ucCnFwQoTMW1emg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-6.3.4.tgz", + "integrity": "sha512-rKEhFXiNH6H2G86JTgy2cgtEFoNBZ50gRy+P1LEhc7Ko/dAYqYMC+Sy8lnbsDzxz6IZVlbubgs+y7GRREayqoQ==", "dev": true, "requires": { "@wdio/logger": "6.0.16", - "@wdio/repl": "6.1.8", - "@wdio/runner": "6.1.16", + "@wdio/repl": "6.3.0", + "@wdio/runner": "6.3.4", "async-exit-hook": "^2.0.1", "stream-buffers": "^3.0.2" } @@ -2382,9 +2359,9 @@ } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -2424,15 +2401,15 @@ } }, "@wdio/mocha-framework": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-6.1.14.tgz", - "integrity": "sha512-2AmUH/v3kZoIDAMdW73AhI4tDJU3ie/2dO/DtpXJ3XFnuJ1CtklZGyCTtYHGMZ8DBj18/8Lrs/O0CjS5bAu2tw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-6.3.0.tgz", + "integrity": "sha512-3lLvzhDYWwOYmiJAjr2fm/nENq6g6uUOtkIeEQFp1kDyBQkDsH1PXGdFklQbRiQT8mAqOPhx1kvXrCA/XpWl7g==", "dev": true, "requires": { "@wdio/logger": "6.0.16", - "@wdio/utils": "6.1.8", + "@wdio/utils": "6.3.0", "expect-webdriverio": "^1.1.5", - "mocha": "^7.0.1" + "mocha": "^8.0.1" }, "dependencies": { "ansi-regex": { @@ -2442,19 +2419,19 @@ "dev": true }, "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.1", + "fsevents": "~2.1.2", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" + "readdirp": "~3.3.0" } }, "cliui": { @@ -2483,28 +2460,11 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -2523,52 +2483,44 @@ } }, "mocha": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", - "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.0.1.tgz", + "integrity": "sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg==", "dev": true, "requires": { - "ansi-colors": "3.2.3", + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.3.0", + "chokidar": "3.3.1", "debug": "3.2.6", - "diff": "3.5.0", + "diff": "4.0.2", "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", + "find-up": "4.1.0", + "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "3.13.1", "log-symbols": "3.0.0", "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", + "ms": "2.1.2", "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", + "promise.allsettled": "1.0.2", + "serialize-javascript": "3.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", "wide-align": "1.1.3", + "workerpool": "6.0.0", "yargs": "13.3.2", "yargs-parser": "13.1.2", "yargs-unparser": "1.6.0" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -2578,19 +2530,19 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", "dev": true, "requires": { - "picomatch": "^2.0.4" + "picomatch": "^2.0.7" } }, "string-width": { @@ -2614,12 +2566,21 @@ } }, "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" } }, "wrap-ansi": { @@ -2649,6 +2610,17 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^13.1.2" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } } }, "yargs-parser": { @@ -2664,51 +2636,51 @@ } }, "@wdio/protocols": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.1.14.tgz", - "integrity": "sha512-UtRLQ55i23cLQRGtFiEJty1F6AbAfiSpfIxDAiXKHbw6Rp1StwxlqHFrhNe5F48Zu4hnie46t9N/tr/cZOe0kA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.3.0.tgz", + "integrity": "sha512-1GKzfyCTLW5WkFd3W7NLACih+zNWU7c8kFurbCQXDK1ko1obqJEs7ZjBr85q5XqMWburdks5rDjyml2iEB2LBg==", "dev": true }, "@wdio/repl": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.1.8.tgz", - "integrity": "sha512-C647KvDIcOHYN24eFbiM2xE+etPEACvRYkEp7BPLyopEABDr0I3Qdb5MLhopC5eMAVHp70/WT27H1CE2v9iILQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.3.0.tgz", + "integrity": "sha512-FT3flKOqNdZNG1uYl+QpOfdZIgKAWhLfoQ0s+wL0crLeDNIFvvM2qSDhRBRDYV7a0IFyBi8Z975WBn0dlH03Ig==", "dev": true, "requires": { - "@wdio/utils": "6.1.8" + "@wdio/utils": "6.3.0" } }, "@wdio/reporter": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-6.1.14.tgz", - "integrity": "sha512-Pt6P0JU0COHTpggsOoJKUJyAyQsi7xlHebBNU/DWdHHpmzYd4e9vDutjyTqXu/1zn+t+Zq+uL1IC0E4Xjv6f7w==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-6.3.0.tgz", + "integrity": "sha512-vbwjJvSKZUtsWtQMhuVqT7ZP6RIFAH4+ienjNwW30QPDi38OujZgxC2ZqRoZKsxck6cfTgkxrXfNaxHN0/LHKg==", "dev": true, "requires": { "fs-extra": "^9.0.0" } }, "@wdio/runner": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-6.1.16.tgz", - "integrity": "sha512-pGRT51BGnxp4zFD1pSp6qZD/4dnbSnDyV4g/MbbFiA4PFKF41mFhaxRwIGMHcp4EYlv9gaT31UA52JaFIYyuNA==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-6.3.4.tgz", + "integrity": "sha512-+iOXfTODsSVf9LFBFKAEZqvPzfIClwFCKu7GGFZ7lrOF1svMNzT/0UY0ETsCBZe61Gr8xiI0wbCEly+0DbEh6w==", "dev": true, "requires": { "@wdio/config": "6.1.14", "@wdio/logger": "6.0.16", - "@wdio/utils": "6.1.8", + "@wdio/utils": "6.3.0", "deepmerge": "^4.0.0", "gaze": "^1.1.2", - "webdriver": "6.1.14", - "webdriverio": "6.1.16" + "webdriver": "6.3.0", + "webdriverio": "6.3.4" } }, "@wdio/spec-reporter": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-6.1.14.tgz", - "integrity": "sha512-QaSBgnzllzLp9LR7/5DTkmrI8BqcznUma8ZxwUNmhvskv/oKzrmNyeCsGoiExFmCk81A9FgZiZPXey7CuaTkdw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-6.3.0.tgz", + "integrity": "sha512-JGZAMcqiOloOw6xcIT5O8GORVaww6kslgH5kZGydVQyoNBj1ZKoLdEjqq2jklJsge1xsscdYdW9u9kMHwm25iA==", "dev": true, "requires": { - "@wdio/reporter": "6.1.14", + "@wdio/reporter": "6.3.0", "chalk": "^4.0.0", "easy-table": "^1.1.1", "pretty-ms": "^7.0.0" @@ -2725,9 +2697,9 @@ } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -2767,19 +2739,20 @@ } }, "@wdio/sync": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-6.1.14.tgz", - "integrity": "sha512-94K0kQdrOU0aMlJ2Xsd4tWr4tPpmCFp612Ml5+ecQh4C4XD07ocfsvGs+mwI7cfF1sO6g/Hoc+XTY2D+/8En3w==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-6.3.3.tgz", + "integrity": "sha512-WNq+hhkgk9LluKLP2nQ/9+EH8HNQnROFFHvYuznxb1aKj/zhZvqWuQPpmMWhPMBSTpkdbdLCYerZWKcamYOcJQ==", "dev": true, "requires": { + "@types/puppeteer": "^3.0.1", "@wdio/logger": "6.0.16", "fibers": "^4.0.1" } }, "@wdio/utils": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.1.8.tgz", - "integrity": "sha512-qzvD8qCPpIpDrZ0HNOx1hTlfKY26p8WByUXgr52ll6DXxtAYXZLIJ8GAYFJUi87NVfwtv6+O7owQGSM/jtr8AQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.3.0.tgz", + "integrity": "sha512-PbeC5fpieamgSAHf7S58MAyraGU1qKxnHdfGMG+ZIWiIo73oo4j/57CcH6ZawQ3YC1wEc/5q+VXg7N5hvqhJOQ==", "dev": true, "requires": { "@wdio/logger": "6.0.16" @@ -2918,9 +2891,9 @@ } }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz", + "integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==", "dev": true }, "align-text": { @@ -2958,9 +2931,9 @@ "dev": true }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-escapes": { @@ -3037,13 +3010,13 @@ } }, "archiver": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-4.0.1.tgz", - "integrity": "sha512-/YV1pU4Nhpf/rJArM23W6GTUjT0l++VbjykrCRua1TSXrn+yM8Qs7XvtwSiRse0iCe49EPNf7ktXnPsWuSb91Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-4.0.2.tgz", + "integrity": "sha512-B9IZjlGwaxF33UN4oPbfBkyA4V1SxNLeIhR1qY8sRXSsbdUkEHrrOvwlYFPx+8uQeCe9M+FG6KgO+imDmQ79CQ==", "dev": true, "requires": { "archiver-utils": "^2.1.0", - "async": "^2.6.3", + "async": "^3.2.0", "buffer-crc32": "^0.2.1", "glob": "^7.1.6", "readable-stream": "^3.6.0", @@ -3052,13 +3025,10 @@ }, "dependencies": { "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true } } }, @@ -3166,6 +3136,12 @@ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -3276,6 +3252,18 @@ "es-abstract": "^1.17.0-next.1" } }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", @@ -3356,9 +3344,9 @@ "dev": true }, "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, "async": { @@ -3424,6 +3412,15 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3787,6 +3784,35 @@ "schema-utils": "^2.6.5" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -3874,15 +3900,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -3892,10 +3909,10 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "semver": { @@ -4636,6 +4653,15 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } } } }, @@ -4907,9 +4933,9 @@ } }, "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, "binaryextensions": { @@ -5173,15 +5199,15 @@ } }, "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" } }, "browserstack": { @@ -5530,9 +5556,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001077", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001077.tgz", - "integrity": "sha512-AEzsGvjBJL0lby/87W96PyEvwN0GsYvk5LHsglLg9tW37K4BqvAvoSCdWIE13OZQ8afupqZ73+oL/1LkedN8hA==", + "version": "1.0.30001105", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001105.tgz", + "integrity": "sha512-JupOe6+dGMr7E20siZHIZQwYqrllxotAhiaej96y6x00b/48rPt42o+SzOSCPbrpsDWvRja40Hwrj0g0q6LZJg==", "dev": true }, "capture-exit": { @@ -5643,9 +5669,9 @@ "dev": true }, "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", "dev": true, "requires": { "anymatch": "~3.1.1", @@ -5665,9 +5691,9 @@ "dev": true }, "chrome-launcher": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.2.tgz", - "integrity": "sha512-zWD9RVVKd8Nx2xKGY4G08lb3nCD+2hmICxovvRE9QjBKQzHFvCYqGlsw15b4zUxLKq3wXEwVbR/yLtMbfk7JbQ==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", "dev": true, "requires": { "@types/node": "*", @@ -5678,6 +5704,15 @@ "rimraf": "^3.0.2" }, "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5744,15 +5779,15 @@ } }, "cli-spinners": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz", - "integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz", + "integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==", "dev": true }, "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, "cliui": { @@ -5903,9 +5938,9 @@ "dev": true }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, "commondir": { @@ -6443,16 +6478,33 @@ } }, "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", + "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", + "dev": true, "requires": { + "es-abstract": "^1.17.5", + "es-get-iterator": "^1.1.0", "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", + "is-date-object": "^1.0.2", + "is-regex": "^1.0.5", + "isarray": "^2.0.5", + "object-is": "^1.1.2", "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" + "object.assign": "^4.1.0", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } } }, "deep-is": { @@ -6519,6 +6571,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -6643,21 +6696,27 @@ } }, "devtools": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.1.16.tgz", - "integrity": "sha512-Px/K/xYY+fTW8D5yt7p6ZZJfkfHHulKVr2Y+BJSCQyKNSY/hiZFT6KAjoUFrAastLCqqs1gW2Dy/OGb0qWm+Hg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.3.4.tgz", + "integrity": "sha512-dOcLdArp5/dJBzD8T5wcT2YgqkA22Mkqo0OS9cXz7JkHNgwOx1FI2Bq9GvP6o0TENHifYSYg3G0K/z0bacekqg==", "dev": true, "requires": { "@wdio/config": "6.1.14", "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.1.14", - "@wdio/utils": "6.1.8", + "@wdio/protocols": "6.3.0", + "@wdio/utils": "6.3.0", "chrome-launcher": "^0.13.1", - "puppeteer-core": "^3.0.0", + "puppeteer-core": "^5.1.0", "ua-parser-js": "^0.7.21", "uuid": "^8.0.0" } }, + "devtools-protocol": { + "version": "0.0.781568", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.781568.tgz", + "integrity": "sha512-9Uqnzy6m6zEStluH9iyJ3iHyaQziFnMnLeC8vK0eN6smiJmIx7+yB64d67C2lH/LZra+5cGscJAJsNXO+MdPMg==", + "dev": true + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -6665,15 +6724,15 @@ "dev": true }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "diff-sequences": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", - "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", + "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", "dev": true }, "diffie-hellman": { @@ -6936,6 +6995,15 @@ } } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "fsevents": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", @@ -6953,6 +7021,12 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", + "dev": true + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -7030,6 +7104,16 @@ "strip-bom": "^3.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -7051,21 +7135,51 @@ "to-regex": "^3.0.2" } }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "p-try": "^1.0.0" } }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, "requires": { "pify": "^3.0.0" } @@ -7177,6 +7291,12 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -7465,15 +7585,15 @@ } }, "electron-to-chromium": { - "version": "1.3.458", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.458.tgz", - "integrity": "sha512-OjRkb0igW0oKE2QbzS7vBYrm7xjW/KRTtIj0OGGx57jr/YhBiKb7oZvdbaojqjfCb/7LbnwsbMbdsYjthdJbAw==", + "version": "1.3.505", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.505.tgz", + "integrity": "sha512-Aunrp3HWtmdiJLIl+IPSFtEvJ/4Q9a3eKaxmzCthaZF1gbTbpHUTCU2zOVnFPH7r/AD7zQXyuFidYXzSHXBdsw==", "dev": true }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -7670,18 +7790,48 @@ "version": "1.17.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } } }, "es-to-primitive": { @@ -7798,6 +7948,12 @@ "es6-symbol": "^3.1.1" } }, + "escalade": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -7810,9 +7966,9 @@ "dev": true }, "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "dev": true, "requires": { "esprima": "^4.0.1", @@ -7916,6 +8072,12 @@ "restore-cursor": "^2.0.0" } }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -7999,6 +8161,15 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8048,6 +8219,12 @@ "requires": { "ansi-regex": "^3.0.0" } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true } } }, @@ -8058,9 +8235,9 @@ "dev": true }, "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { "debug": "^2.6.9", @@ -8077,6 +8254,55 @@ "pkg-dir": "^2.0.0" }, "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -8089,25 +8315,35 @@ } }, "eslint-plugin-import": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", - "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", + "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -8120,6 +8356,46 @@ "strip-bom": "^3.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -8205,9 +8481,9 @@ } }, "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { @@ -8301,9 +8577,9 @@ "dev": true }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "evp_bytestokey": { @@ -8469,17 +8745,17 @@ } }, "expect": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz", - "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.1.0.tgz", + "integrity": "sha512-QbH4LZXDsno9AACrN9eM0zfnby9G+OsdNgZUohjg/P0mLy1O+/bzTAJGT6VSIjVCe8yKM6SzEl/ckEOFBT7Vnw==", "dev": true, "requires": { - "@jest/types": "^25.5.0", + "@jest/types": "^26.1.0", "ansi-styles": "^4.0.0", - "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.5.0", - "jest-message-util": "^25.5.0", - "jest-regex-util": "^25.2.6" + "jest-get-type": "^26.0.0", + "jest-matcher-utils": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-regex-util": "^26.0.0" }, "dependencies": { "ansi-styles": { @@ -8510,13 +8786,13 @@ } }, "expect-webdriverio": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-1.1.5.tgz", - "integrity": "sha512-+RL4Lkne+/z+o1G5Y0S5QuEXeICxt4jExhBSM2Jn/mrDwb+PZVKPM2Yd1OzLsKeCdQLtw4Oft6514Gp5GLgdZA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-1.2.0.tgz", + "integrity": "sha512-nis1EL4LJSKvhqES6ojx1QqAZYtWAUHaVtwilXBXJELN2YZhwOcrfBT0AvxDvJXYKzgDDTED9STc9MwcK8KbYg==", "dev": true, "requires": { - "expect": "^25.2.1", - "jest-matcher-utils": "^25.1.0" + "expect": "^26.0.1", + "jest-matcher-utils": "^26.0.1" } }, "express": { @@ -8677,9 +8953,9 @@ } }, "extract-zip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.0.tgz", - "integrity": "sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "requires": { "@types/yauzl": "^2.9.1", @@ -8730,9 +9006,9 @@ } }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { @@ -8872,15 +9148,61 @@ "commondir": "^1.0.1", "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "findup-sync": { @@ -9091,30 +9413,10 @@ } }, "follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", - "dev": true, - "requires": { - "debug": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", + "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==", + "dev": true }, "for-in": { "version": "1.0.2", @@ -9131,6 +9433,12 @@ "for-in": "^1.0.1" } }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, "foreachasync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", @@ -9228,9 +9536,9 @@ "dev": true }, "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -9315,6 +9623,17 @@ "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } } }, "fun-hooks": { @@ -9328,7 +9647,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -9364,9 +9684,9 @@ "dev": true }, "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true }, "get-stdin": { @@ -9550,9 +9870,9 @@ } }, "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -9560,6 +9880,7 @@ "chokidar": "^2.0.0", "is-negated-glob": "^1.0.0", "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", "object.defaults": "^1.1.0" }, "dependencies": { @@ -9571,6 +9892,17 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "binary-extensions": { @@ -9626,14 +9958,6 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } } }, "fill-range": { @@ -9747,15 +10071,6 @@ "to-regex": "^3.0.2" } }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -9831,13 +10146,13 @@ "dev": true }, "globule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", - "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", "dev": true, "requires": { "glob": "~7.1.1", - "lodash": "~4.17.12", + "lodash": "~4.17.10", "minimatch": "~3.0.2" } }, @@ -9851,20 +10166,19 @@ } }, "got": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/got/-/got-11.2.0.tgz", - "integrity": "sha512-68pBow9XXXSdVRV5wSx0kWMCZsag4xE3Ru0URVe0PWsSYmU4SJrUmEO6EVYFlFHc9rq/6Yqn6o1GxIb9torQxg==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/got/-/got-11.5.1.tgz", + "integrity": "sha512-reQEZcEBMTGnujmQ+Wm97mJs/OK6INtO6HmLI+xt3+9CvnRwWjXutUvb2mqr+Ao4Lu05Rx6+udx9sOQAmExMxA==", "dev": true, "requires": { - "@sindresorhus/is": "^2.1.1", + "@sindresorhus/is": "^3.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.1", "decompress-response": "^6.0.0", - "get-stream": "^5.1.0", - "http2-wrapper": "^1.0.0-beta.4.5", + "http2-wrapper": "^1.0.0-beta.5.0", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" @@ -9945,9 +10259,9 @@ "dev": true }, "gulp-cli": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.1.tgz", - "integrity": "sha512-yEMxrXqY8mJFlaauFQxNrCpzWJThu0sH1sqlToaTOT063Hub9s/Nt2C+GSLe6feQ/IMWrHvGOOsyES7CQc9O+A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -9958,7 +10272,7 @@ "copy-props": "^2.0.1", "fancy-log": "^1.3.2", "gulplog": "^1.0.0", - "interpret": "^1.1.0", + "interpret": "^1.4.0", "isobject": "^3.0.1", "liftoff": "^3.1.0", "matchdep": "^2.0.0", @@ -9966,7 +10280,7 @@ "pretty-hrtime": "^1.0.0", "replace-homedir": "^1.0.0", "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", + "v8flags": "^3.2.0", "yargs": "^7.1.0" } }, @@ -10832,9 +11146,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -10849,6 +11163,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -10917,7 +11232,8 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true }, "has-to-string-tag-x": { "version": "1.4.1", @@ -11191,12 +11507,12 @@ } }, "http2-wrapper": { - "version": "1.0.0-beta.4.6", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.4.6.tgz", - "integrity": "sha512-9oB4BiGDTI1FmIBlOF9OJ5hwJvcBEmPCqk/hy314Uhy2uq5TjekUZM8w8SPLLlUEM+mxNhXdPAXfrJN2Zbb/GQ==", + "version": "1.0.0-beta.5.2", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", + "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", "dev": true, "requires": { - "quick-lru": "^5.0.0", + "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, @@ -11261,6 +11577,51 @@ "requires": { "pkg-dir": "^3.0.0", "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, "imurmurhash": { @@ -11303,21 +11664,21 @@ "dev": true }, "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", + "chalk": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", + "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "mute-stream": "0.0.8", "run-async": "^2.4.0", - "rxjs": "^6.5.3", + "rxjs": "^6.6.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6" @@ -11334,9 +11695,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -11472,7 +11833,8 @@ "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true }, "is-arrayish": { "version": "0.2.1", @@ -11480,6 +11842,12 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", + "dev": true + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -11489,6 +11857,12 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "dev": true + }, "is-buffer": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", @@ -11498,7 +11872,8 @@ "is-callable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true }, "is-ci": { "version": "2.0.0", @@ -11538,7 +11913,8 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true }, "is-decimal": { "version": "1.0.4", @@ -11631,6 +12007,12 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -11643,6 +12025,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, "is-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", @@ -11686,8 +12074,9 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, "requires": { - "has": "^1.0.3" + "has-symbols": "^1.0.1" } }, "is-relative": { @@ -11717,6 +12106,12 @@ "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", "dev": true }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, "is-ssh": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", @@ -11742,7 +12137,20 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "dev": true, "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", "has-symbols": "^1.0.1" } }, @@ -11773,6 +12181,18 @@ "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", "dev": true }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "dev": true + }, "is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -11913,6 +12333,15 @@ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", @@ -12038,6 +12467,15 @@ "handlebars": "^4.0.3" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -12208,10 +12646,26 @@ "is-object": "^1.0.1" } }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, "jake": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.1.tgz", - "integrity": "sha512-eSp5h9S7UFzKdQERTyF+KuPLjDZa1Tbw8gCVUn98n4PbIkLEDGe4zl7vF4Qge9kQj06HcymnksPk8jznPZeKsA==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", "dev": true, "requires": { "async": "0.9.x", @@ -12319,15 +12773,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -12337,10 +12782,10 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "string-width": { @@ -12624,15 +13069,15 @@ } }, "jest-diff": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", - "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.1.0.tgz", + "integrity": "sha512-GZpIcom339y0OXznsEKjtkfKxNdg7bVbEofK8Q6MnevTIiR1jNhDWKhRX6X0SDXJlwn3dy59nZ1z55fLkAqPWg==", "dev": true, "requires": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.0" + "chalk": "^4.0.0", + "diff-sequences": "^26.0.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.1.0" }, "dependencies": { "ansi-styles": { @@ -12646,9 +13091,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -12827,9 +13272,9 @@ } }, "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", "dev": true }, "jest-haste-map": { @@ -13252,6 +13697,12 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -13321,15 +13772,15 @@ } }, "jest-matcher-utils": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz", - "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.1.0.tgz", + "integrity": "sha512-PW9JtItbYvES/xLn5mYxjMd+Rk+/kIt88EfH3N7w9KeOrHWaHrdYPnVHndGbsFGRJ2d5gKtwggCvkqbFDoouQA==", "dev": true, "requires": { - "chalk": "^3.0.0", - "jest-diff": "^25.5.0", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.0" + "chalk": "^4.0.0", + "jest-diff": "^26.1.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.1.0" }, "dependencies": { "ansi-styles": { @@ -13343,9 +13794,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -13385,19 +13836,19 @@ } }, "jest-message-util": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", - "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.1.0.tgz", + "integrity": "sha512-dY0+UlldiAJwNDJ08SF0HdF32g9PkbF2NRK/+2iMPU40O6q+iSn1lgog/u0UH8ksWoPv0+gNq8cjhYO2MFtT0g==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/types": "^25.5.0", + "@jest/types": "^26.1.0", "@types/stack-utils": "^1.0.1", - "chalk": "^3.0.0", + "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "slash": "^3.0.0", - "stack-utils": "^1.0.1" + "stack-utils": "^2.0.2" }, "dependencies": { "ansi-styles": { @@ -13411,9 +13862,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -13484,15 +13935,15 @@ } }, "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", "dev": true }, "jest-regex-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", - "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", "dev": true }, "jest-resolve": { @@ -13753,6 +14204,12 @@ "source-map": "^0.6.0" } }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -13985,15 +14442,6 @@ "to-regex": "^3.0.2" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -14003,10 +14451,10 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "slash": { @@ -14015,6 +14463,12 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -14316,6 +14770,15 @@ "to-regex": "^3.0.2" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "pretty-format": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", @@ -14340,6 +14803,12 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -14392,6 +14861,15 @@ "@types/yargs-parser": "*" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -15051,6 +15529,26 @@ "requires": { "lodash": "^4.17.14" } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } } } }, @@ -15247,41 +15745,29 @@ "dev": true }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } + "json5": "^2.1.2" } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash._basecopy": { @@ -15592,18 +16078,6 @@ "chalk": "^2.4.2" } }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - } - }, "log4js": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", @@ -16315,13 +16789,10 @@ } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "mkdirp-classic": { "version": "0.5.3", @@ -16348,12 +16819,6 @@ "supports-color": "5.4.0" }, "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -16363,6 +16828,12 @@ "ms": "2.0.0" } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -16630,9 +17101,9 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, "next-tick": { @@ -16696,16 +17167,6 @@ } } }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -16805,9 +17266,9 @@ } }, "node-releases": { - "version": "1.1.58", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", - "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==", + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", "dev": true }, "nopt": { @@ -17021,14 +17482,15 @@ } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" }, "object-is": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -17037,7 +17499,8 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object-visit": { "version": "1.0.1", @@ -17052,6 +17515,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -17353,21 +17817,21 @@ "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.2.0" } }, "p-reduce": { @@ -17386,9 +17850,9 @@ } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "pako": { @@ -17528,6 +17992,15 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "normalize-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", @@ -17762,9 +18235,9 @@ "dev": true }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-is-absolute": { @@ -17844,9 +18317,9 @@ } }, "pbkdf2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.0.tgz", - "integrity": "sha512-wHMFZ6HTLGlB9f/WsQBs5OwMQJoLXYuJUzbA+j+hRBf7+Y8KcXpatzIviIcTy1OAyhWQp08nyiPO8Dnv0z4Sww==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -17917,66 +18390,12 @@ } }, "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - } - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "find-up": "^2.1.0" + "find-up": "^4.0.0" } }, "plugin-error": { @@ -18039,12 +18458,12 @@ "dev": true }, "pretty-format": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", - "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.1.0.tgz", + "integrity": "sha512-GmeO1PEYdM+non4BKCj+XsPJjFOJIPnsLewqhDVoqY1xo0yNmDas7tC2XwpMrRAHR3MaE2hPo37deX5OisJ2Wg==", "dev": true, "requires": { - "@jest/types": "^25.5.0", + "@jest/types": "^26.1.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -18116,6 +18535,19 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, "prompts": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", @@ -18242,15 +18674,17 @@ "dev": true }, "puppeteer-core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-3.3.0.tgz", - "integrity": "sha512-hynQ3r0J/lkGrKeBCqu160jrj0WhthYLIzDQPkBxLzxPokjw4elk1sn6mXAian/kfD2NRzpdh9FSykxZyL56uA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.2.1.tgz", + "integrity": "sha512-gLjEOrzwgcnwRH+sm4hS1TBqe2/DN248nRb2hYB7+lZ9kCuLuACNvuzlXILlPAznU3Ob+mEvVEBDcLuFa0zq3g==", "dev": true, "requires": { "debug": "^4.1.0", + "devtools-protocol": "0.0.781568", "extract-zip": "^2.0.0", "https-proxy-agent": "^4.0.0", "mime": "^2.0.3", + "pkg-dir": "^4.2.0", "progress": "^2.0.1", "proxy-from-env": "^1.0.0", "rimraf": "^3.0.2", @@ -18482,6 +18916,15 @@ "resolve": "^1.1.6" } }, + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "dev": true, + "requires": { + "minimatch": "3.0.4" + } + }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -18504,9 +18947,9 @@ } }, "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", "dev": true }, "regenerate-unicode-properties": { @@ -18524,13 +18967,12 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" + "@babel/runtime": "^7.8.4" } }, "regex-cache": { @@ -18556,6 +18998,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" @@ -18855,21 +19298,21 @@ } }, "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "lodash": "^4.17.15" + "lodash": "^4.17.19" } }, "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", "dev": true, "requires": { - "request-promise-core": "1.1.3", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" } @@ -19083,9 +19526,9 @@ } }, "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -19287,9 +19730,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -19359,6 +19802,12 @@ } } }, + "serialize-javascript": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz", + "integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==", + "dev": true + }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -19493,6 +19942,16 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -19512,6 +19971,14 @@ "nise": "^1.2.0", "supports-color": "^5.1.0", "type-detect": "^4.0.5" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + } } }, "sisteransi": { @@ -19527,39 +19994,18 @@ "dev": true }, "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true } } @@ -19922,10 +20368,21 @@ "dev": true }, "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } }, "state-toggle": { "version": "1.0.3", @@ -20201,12 +20658,6 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -20249,35 +20700,17 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, "string.prototype.trimstart": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -20344,9 +20777,9 @@ } }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, "subarg": { @@ -20421,15 +20854,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -20470,9 +20894,9 @@ } }, "tar-stream": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", - "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", + "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", "dev": true, "requires": { "bl": "^4.0.1", @@ -20572,15 +20996,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -20590,12 +21005,6 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -20606,6 +21015,12 @@ "json-parse-better-errors": "^1.0.1" } }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -20949,6 +21364,35 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "dev": true }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", @@ -21046,13 +21490,10 @@ "dev": true }, "uglify-js": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.4.tgz", - "integrity": "sha512-8RZBJq5smLOa7KslsNsVcSH+KOXf1uDU8yqLeNuVKwmT0T3FA0ZoXlinQfRad7SDcbZZRZE4ov+2v71EnxNyCA==", - "dev": true, - "requires": { - "commander": "~2.20.3" - } + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", + "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "dev": true }, "uglify-to-browserify": { "version": "1.0.2", @@ -21516,9 +21957,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", - "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", "dev": true }, "v8flags": { @@ -22088,41 +22529,46 @@ } }, "webdriver": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.1.14.tgz", - "integrity": "sha512-6fXoGDnxWfJn9zqfYbc6+VEV5N1obd3K7iuRmJ7w/hH9d9XDxcrcx147T5egiy1qziko61hqYE/x9VtSQbjPcA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.3.0.tgz", + "integrity": "sha512-osHp5DX8eQ76Sy6/UYoECmDnUXwLhy5tlBeJh86er7S04FLAlmEqCvYXgxTSb8YtDjh9Xt9gP768RGhxR7ik5A==", "dev": true, "requires": { "@wdio/config": "6.1.14", "@wdio/logger": "6.0.16", - "@wdio/protocols": "6.1.14", - "@wdio/utils": "6.1.8", + "@wdio/protocols": "6.3.0", + "@wdio/utils": "6.3.0", "got": "^11.0.2", "lodash.merge": "^4.6.1" } }, "webdriverio": { - "version": "6.1.16", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.1.16.tgz", - "integrity": "sha512-vk6/XeErNnMooebCtxwIR//LqrastXO9gXL+eVUGNRAuG5omp8XCdT+MK2fmlw53xhcPULQ/y3h8ysYlPnPeyA==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.3.4.tgz", + "integrity": "sha512-/53xQEituEFTaJtZMgg5Uz3GXY1Otqyry0LA8dYLYUNkTK0yCa26DL4ycDnWE0i9wEYNFX6YHCgiqTJjHEjKAg==", "dev": true, "requires": { + "@types/puppeteer": "^3.0.1", "@wdio/config": "6.1.14", "@wdio/logger": "6.0.16", - "@wdio/repl": "6.1.8", - "@wdio/utils": "6.1.8", + "@wdio/repl": "6.3.0", + "@wdio/utils": "6.3.0", "archiver": "^4.0.1", + "atob": "^2.1.2", "css-value": "^0.0.1", - "devtools": "6.1.16", + "devtools": "6.3.4", + "get-port": "^5.1.1", "grapheme-splitter": "^1.0.2", "lodash.clonedeep": "^4.5.0", "lodash.isobject": "^3.0.2", "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^5.1.0", "resq": "^1.6.0", "rgb2hex": "^0.2.0", "serialize-error": "^7.0.0", - "webdriver": "6.1.14" + "webdriver": "6.3.0" } }, "webidl-conversions": { @@ -22162,9 +22608,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -22218,6 +22664,15 @@ } } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -22257,6 +22712,77 @@ "strip-bom": "^3.0.0" } }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -22433,15 +22959,21 @@ }, "dependencies": { "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", "dev": true }, "acorn-walk": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", - "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "ejs": { @@ -22450,6 +22982,15 @@ "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ws": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", @@ -22974,6 +23515,15 @@ "regex-cache": "^0.4.2" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "node-libs-browser": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.7.0.tgz", @@ -23476,12 +24026,51 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", + "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "dev": true, + "requires": { + "is-bigint": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.2" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -23542,6 +24131,12 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -23593,6 +24188,17 @@ "dev": true, "requires": { "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } } }, "write-file-atomic": { @@ -23607,9 +24213,9 @@ } }, "ws": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", - "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", "dev": true }, "x-is-string": { @@ -23723,15 +24329,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -23741,10 +24338,10 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "string-width": { diff --git a/package.json b/package.json index 7fef561be33..3cb04dc6f8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "3.27.0-pre", + "version": "4.4.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -39,6 +39,7 @@ "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", + "deep-equal": "^2.0.3", "documentation": "^5.2.2", "es5-shim": "^4.5.14", "eslint-config-standard": "^10.2.1", @@ -105,7 +106,6 @@ "core-js-pure": "^3.6.5", "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^3.3.0", - "deep-equal": "^1.0.1", "dlv": "1.1.3", "dset": "2.0.1", "express": "^4.15.4", diff --git a/src/adapterManager.js b/src/adapterManager.js index 2108bb7a4f6..5124ae99694 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -426,7 +426,7 @@ adapterManager.registerBidAdapter = function (bidAdaptor, bidderCode, {supported } }; -adapterManager.aliasBidAdapter = function (bidderCode, alias) { +adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { let existingAlias = _bidderRegistry[alias]; if (typeof existingAlias === 'undefined') { @@ -452,7 +452,8 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias) { newAdapter.setBidderCode(alias); } else { let spec = bidAdaptor.getSpec(); - newAdapter = newBidder(Object.assign({}, spec, { code: alias })); + let gvlid = options && options.gvlid; + newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid })); _aliasRegistry[alias] = bidderCode; } adapterManager.registerBidAdapter(newAdapter, alias, { @@ -467,11 +468,11 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias) { } }; -adapterManager.registerAnalyticsAdapter = function ({adapter, code}) { +adapterManager.registerAnalyticsAdapter = function ({adapter, code, gvlid}) { if (adapter && code) { if (typeof adapter.enableAnalytics === 'function') { adapter.code = code; - _analyticsRegistry[code] = adapter; + _analyticsRegistry[code] = { adapter, gvlid }; } else { utils.logError(`Prebid Error: Analytics adaptor error for analytics "${code}" analytics adapter must implement an enableAnalytics() function`); @@ -487,7 +488,7 @@ adapterManager.enableAnalytics = function (config) { } utils._each(config, adapterConfig => { - var adapter = _analyticsRegistry[adapterConfig.provider]; + var adapter = _analyticsRegistry[adapterConfig.provider].adapter; if (adapter) { adapter.enableAnalytics(adapterConfig); } else { @@ -495,12 +496,16 @@ adapterManager.enableAnalytics = function (config) { ${adapterConfig.provider}.`); } }); -}; +} adapterManager.getBidAdapter = function(bidder) { return _bidderRegistry[bidder]; }; +adapterManager.getAnalyticsAdapter = function(code) { + return _analyticsRegistry[code]; +} + // the s2sTesting module is injected when it's loaded rather than being imported // importing it causes the packager to include it even when it's not explicitly included in the build export function setS2STestingModule(module) { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 8dd351563be..3b6260efc88 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -9,7 +9,7 @@ import CONSTANTS from '../constants.json'; import events from '../events.js'; import includes from 'core-js-pure/features/array/includes.js'; import { ajax } from '../ajax.js'; -import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest, flatten, uniques, timestamp, deepAccess, isArray } from '../utils.js'; +import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, getBidderRequest, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; import { ADPOD } from '../mediaTypes.js'; import { getHook, hook } from '../hook.js'; import { getCoreStorageManager } from '../storageManager.js'; @@ -153,8 +153,14 @@ export function registerBidder(spec) { putBidder(spec); if (Array.isArray(spec.aliases)) { spec.aliases.forEach(alias => { - adapterManager.aliasRegistry[alias] = spec.code; - putBidder(Object.assign({}, spec, { code: alias })); + let aliasCode = alias; + let gvlid; + if (isPlainObject(alias)) { + aliasCode = alias.code; + gvlid = alias.gvlid; + } + adapterManager.aliasRegistry[aliasCode] = spec.code; + putBidder(Object.assign({}, spec, { code: aliasCode, gvlid })); }); } } @@ -307,6 +313,7 @@ export function newBidder(spec) { // creating a copy of original values as cpm and currency are modified later bid.originalCpm = bid.cpm; bid.originalCurrency = bid.currency; + bid.meta = bid.meta || Object.assign({}, bid[bidRequest.bidder]); const prebidBid = Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid); addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { diff --git a/src/constants.json b/src/constants.json index 5965d77a6c4..1b5feda6a05 100644 --- a/src/constants.json +++ b/src/constants.json @@ -36,7 +36,8 @@ "BEFORE_REQUEST_BIDS": "beforeRequestBids", "REQUEST_BIDS": "requestBids", "ADD_AD_UNITS": "addAdUnits", - "AD_RENDER_FAILED" : "adRenderFailed" + "AD_RENDER_FAILED": "adRenderFailed", + "TCF2_ENFORCEMENT": "tcf2Enforcement" }, "AD_RENDER_FAILED_REASON" : { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocuemnt", diff --git a/src/prebid.js b/src/prebid.js index 1710849ba92..827ba2e2270 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -83,50 +83,58 @@ function validateSizes(sizes, targLength) { } function validateBannerMediaType(adUnit) { - const banner = adUnit.mediaTypes.banner; + const validatedAdUnit = utils.deepClone(adUnit); + const banner = validatedAdUnit.mediaTypes.banner; const bannerSizes = validateSizes(banner.sizes); if (bannerSizes.length > 0) { banner.sizes = bannerSizes; // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.banner.sizes - adUnit.sizes = bannerSizes; + validatedAdUnit.sizes = bannerSizes; } else { utils.logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); - delete adUnit.mediaTypes.banner + delete validatedAdUnit.mediaTypes.banner } + return validatedAdUnit; } function validateVideoMediaType(adUnit) { - const video = adUnit.mediaTypes.video; - let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; - - const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen); - if (videoSizes.length > 0) { - if (tarPlayerSizeLen === 2) { - utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); + const validatedAdUnit = utils.deepClone(adUnit); + const video = validatedAdUnit.mediaTypes.video; + if (video.playerSize) { + let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; + + const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen); + if (videoSizes.length > 0) { + if (tarPlayerSizeLen === 2) { + utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); + } + video.playerSize = videoSizes; + // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize + validatedAdUnit.sizes = videoSizes; + } else { + utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'); + delete validatedAdUnit.mediaTypes.video.playerSize; } - video.playerSize = videoSizes; - // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize - adUnit.sizes = videoSizes; - } else { - utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'); - delete adUnit.mediaTypes.video.playerSize; } + return validatedAdUnit; } function validateNativeMediaType(adUnit) { - const native = adUnit.mediaTypes.native; + const validatedAdUnit = utils.deepClone(adUnit); + const native = validatedAdUnit.mediaTypes.native; if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { utils.logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); - delete adUnit.mediaTypes.native.image.sizes; + delete validatedAdUnit.mediaTypes.native.image.sizes; } if (native.image && native.image.aspect_ratios && !Array.isArray(native.image.aspect_ratios)) { utils.logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.'); - delete adUnit.mediaTypes.native.image.aspect_ratios; + delete validatedAdUnit.mediaTypes.native.image.aspect_ratios; } if (native.icon && native.icon.sizes && !Array.isArray(native.icon.sizes)) { utils.logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); - delete adUnit.mediaTypes.native.icon.sizes; + delete validatedAdUnit.mediaTypes.native.icon.sizes; } + return validatedAdUnit; } export const adUnitSetupChecks = { @@ -137,29 +145,34 @@ export const adUnitSetupChecks = { }; export const checkAdUnitSetup = hook('sync', function (adUnits) { - return adUnits.filter(adUnit => { + const validatedAdUnits = []; + + adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + let validatedBanner, validatedVideo, validatedNative; if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); - return false; + return; } if (mediaTypes.banner) { - validateBannerMediaType(adUnit); + validatedBanner = validateBannerMediaType(adUnit); } if (mediaTypes.video) { - const video = mediaTypes.video; - if (video.playerSize) { - validateVideoMediaType(adUnit); - } + validatedVideo = validatedBanner ? validateVideoMediaType(validatedBanner) : validateVideoMediaType(adUnit); } if (mediaTypes.native) { - validateNativeMediaType(adUnit); + validatedNative = validatedVideo ? validateNativeMediaType(validatedVideo) : validatedBanner ? validateNativeMediaType(validatedBanner) : validateNativeMediaType(adUnit); } - return true; + + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + + validatedAdUnits.push(validatedAdUnit); }); + + return validatedAdUnits; }, 'checkAdUnitSetup'); /// /////////////////////////////// @@ -192,7 +205,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { +$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { return $$PREBID_GLOBAL$$.getAdserverTargeting(adUnitCode)[adUnitCode]; }; @@ -300,7 +313,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ -$$PREBID_GLOBAL$$.setTargetingForAst = function(adUnitCodes) { +$$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { utils.logError('window.apntag is not defined on the page'); @@ -448,6 +461,8 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + adUnits = checkAdUnitSetup(adUnits); + if (adUnitCodes && adUnitCodes.length) { // if specific adUnitCodes supplied filter adUnits for those codes adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); @@ -456,8 +471,6 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo adUnitCodes = adUnits && adUnits.map(unit => unit.code); } - adUnits = checkAdUnitSetup(adUnits); - /* * for a given adunit which supports a set of mediaTypes * and a given bidder which supports a set of mediaTypes @@ -466,7 +479,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo */ adUnits.forEach(adUnit => { // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present - const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}); + const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || { 'banner': 'banner' }); // get the bidder's mediaTypes const allBidders = adUnit.bids.map(bid => bid.bidder); @@ -512,7 +525,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo return; } - const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId}); + const auction = auctionManager.createAuction({ adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId }); let adUnitsLen = adUnits.length; if (adUnitsLen > 15) { @@ -523,8 +536,9 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo auction.callBids(); }); -export function executeStorageCallbacks(fn, reqBidsConfigObj) { +export function executeCallbacks(fn, reqBidsConfigObj) { runAll(storageCallbacks); + runAll(enableAnalyticsCallbacks); fn.call(this, reqBidsConfigObj); function runAll(queue) { var queued; @@ -535,7 +549,7 @@ export function executeStorageCallbacks(fn, reqBidsConfigObj) { } // This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that gdprEnforcement module is added or not -$$PREBID_GLOBAL$$.requestBids.before(executeStorageCallbacks, 49); +$$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); /** * @@ -654,22 +668,30 @@ $$PREBID_GLOBAL$$.createBid = function (statusCode) { * @param {Object} config.options The options for this particular analytics adapter. This will likely vary between adapters. * @alias module:pbjs.enableAnalytics */ -$$PREBID_GLOBAL$$.enableAnalytics = function (config) { + +// Stores 'enableAnalytics' callbacks for later execution. +const enableAnalyticsCallbacks = []; + +const enableAnalyticsCb = hook('async', function (config) { if (config && !utils.isEmpty(config)) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.enableAnalytics for: ', config); adapterManager.enableAnalytics(config); } else { utils.logError('$$PREBID_GLOBAL$$.enableAnalytics should be called with option {}'); } +}, 'enableAnalyticsCb'); + +$$PREBID_GLOBAL$$.enableAnalytics = function (config) { + enableAnalyticsCallbacks.push(enableAnalyticsCb.bind(this, config)); }; /** * @alias module:pbjs.aliasBidder */ -$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) { +$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { - adapterManager.aliasBidAdapter(bidderCode, alias); + adapterManager.aliasBidAdapter(bidderCode, alias, options); } else { utils.logError('bidderCode and alias must be passed as arguments', '$$PREBID_GLOBAL$$.aliasBidder'); } @@ -843,7 +865,7 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * the Prebid script has been fully loaded. * @alias module:pbjs.cmd.push */ -$$PREBID_GLOBAL$$.cmd.push = function(command) { +$$PREBID_GLOBAL$$.cmd.push = function (command) { if (typeof command === 'function') { try { command.call(); @@ -858,7 +880,7 @@ $$PREBID_GLOBAL$$.cmd.push = function(command) { $$PREBID_GLOBAL$$.que.push = $$PREBID_GLOBAL$$.cmd.push; function processQueue(queue) { - queue.forEach(function(cmd) { + queue.forEach(function (cmd) { if (typeof cmd.called === 'undefined') { try { cmd.call(); @@ -873,7 +895,7 @@ function processQueue(queue) { /** * @alias module:pbjs.processQueue */ -$$PREBID_GLOBAL$$.processQueue = function() { +$$PREBID_GLOBAL$$.processQueue = function () { hook.ready(); processQueue($$PREBID_GLOBAL$$.que); processQueue($$PREBID_GLOBAL$$.cmd); diff --git a/src/targeting.js b/src/targeting.js index 45c098554a5..5873eeeb559 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -44,7 +44,7 @@ export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, a Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); // if adUnitBidLimit is set, pass top N number bids if (adUnitBidLimit > 0) { - bucketBids = dealPrioritization ? bucketBids(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); + bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); bids.push(...bucketBids.slice(0, adUnitBidLimit)); } else { bids.push(...bucketBids); @@ -76,11 +76,11 @@ export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, a */ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { return function(a, b) { - if (a.adUnitTargeting.hb_deal !== undefined && b.adUnitTargeting.hb_deal === undefined) { + if (a.adserverTargeting.hb_deal !== undefined && b.adserverTargeting.hb_deal === undefined) { return -1; } - if ((a.adUnitTargeting.hb_deal === undefined && b.adUnitTargeting.hb_deal !== undefined)) { + if ((a.adserverTargeting.hb_deal === undefined && b.adserverTargeting.hb_deal !== undefined)) { return 1; } @@ -89,7 +89,7 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { return b.cpm - a.cpm; } - return b.adUnitTargeting.hb_pb - a.adUnitTargeting.hb_pb; + return b.adserverTargeting.hb_pb - a.adserverTargeting.hb_pb; } } @@ -240,13 +240,13 @@ export function newTargeting(auctionManager) { let targetingMap = Object.keys(targetingCopy).map(adUnitCode => { return { adUnitCode, - adUnitTargeting: targetingCopy[adUnitCode] + adserverTargeting: targetingCopy[adUnitCode] }; }).sort(sortByDealAndPriceBucketOrCpm()); // iterate through the targeting based on above list and transform the keys into the query-equivalent and count characters return targetingMap.reduce(function (accMap, currMap, index, arr) { - let adUnitQueryString = convertKeysToQueryForm(currMap.adUnitTargeting); + let adUnitQueryString = convertKeysToQueryForm(currMap.adserverTargeting); // for the last adUnit - trim last encoded ampersand from the converted query string if ((index + 1) === arr.length) { diff --git a/src/utils.js b/src/utils.js index 88328ff10aa..f0fa57c4cff 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,6 @@ /* eslint-disable no-console */ import { config } from './config.js'; import clone from 'just-clone'; -import deepequal from 'deep-equal'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -1169,13 +1168,28 @@ export function buildUrl(obj) { } /** - * This function compares two objects for checking their equivalence. + * This function deeply compares two objects checking for their equivalence. * @param {Object} obj1 * @param {Object} obj2 * @returns {boolean} */ export function deepEqual(obj1, obj2) { - return deepequal(obj1, obj2); + if (obj1 === obj2) return true; + else if ((typeof obj1 === 'object' && obj1 !== null) && (typeof obj2 === 'object' && obj2 !== null)) { + if (Object.keys(obj1).length !== Object.keys(obj2).length) return false; + for (let prop in obj1) { + if (obj2.hasOwnProperty(prop)) { + if (!deepEqual(obj1[prop], obj2[prop])) { + return false; + } + } else { + return false; + } + } + return true; + } else { + return false; + } } export function mergeDeep(target, ...sources) { diff --git a/test/fixtures/video/adUnit.json b/test/fixtures/video/adUnit.json index df55eb25d79..0773d7f3a62 100644 --- a/test/fixtures/video/adUnit.json +++ b/test/fixtures/video/adUnit.json @@ -1,7 +1,11 @@ { "code": "video1", - "sizes": [640,480], - "mediaType": "video", + "mediaTypes": { + "video": { + "context": "instream", + "playerSize": [640, 480] + } + }, "bids": [ { "bidder": "appnexus", diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 3721cef18d9..d30659791ea 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -162,7 +162,7 @@ describe('33acrossBidAdapter:', function () { function ServerRequestBuilder() { const serverRequest = { 'method': 'POST', - 'url': END_POINT, + 'url': `${END_POINT}?guid=${SITE_ID}`, 'data': null, 'options': { 'contentType': 'text/plain', diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 97265121ce0..52b99f274d5 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,678 +1,586 @@ +import find from 'core-js-pure/features/array/find.js'; import { expect } from 'chai'; -import { adagioScriptFromLocalStorageCb, _features, spec } from 'modules/adagioBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as utils from 'src/utils.js'; +import { _features, internal as adagio, adagioScriptFromLocalStorageCb, getAdagioScript, storage, spec, ENDPOINT, VERSION } from '../../../modules/adagioBidAdapter.js'; +import { loadExternalScript } from '../../../src/adloader.js'; +import * as utils from '../../../src/utils.js'; + +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'adagio' + }, + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code' + }, + sizes: [[300, 250], [300, 600]], + }; + + const request = { + ...defaults.request, + ...options + }; + + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; + }; + + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'adagio', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }; -describe('adagioAdapter', () => { - let utilsMock; - const adapter = newBidder(spec); - const ENDPOINT = 'https://mp.4dex.io/prebid'; - const VERSION = '2.2.2'; + const request = { + ...defaults, + ...options + }; - beforeEach(function() { - localStorage.removeItem('adagioScript'); - utilsMock = sinon.mock(utils); - }); + this.build = () => request; +}; - afterEach(function() { - utilsMock.restore(); - }); - - describe('inherited functions', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); +describe('Adagio bid adapter', () => { + let adagioMock; + let utilsMock; + let sandbox; + + const fixtures = { + getElementById(width, height, x, y) { + const obj = { + x: x || 800, + y: y || 300, + width: width || 300, + height: height || 250, + }; - describe('isBidRequestValid', () => { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - let element = { - x: 0, - y: 0, - width: 200, - height: 300, + return { + ...obj, getBoundingClientRect: () => { return { - width: element.width, - height: element.height, - left: element.x, - top: element.y, - right: element.x + element.width, - bottom: element.y + element.height + width: obj.width, + height: obj.height, + left: obj.x, + top: obj.y, + right: obj.x + obj.width, + bottom: obj.y + obj.height }; } }; - sandbox.stub(document, 'getElementById').withArgs('banner-atf').returns(element); - }); + } + }; - afterEach(function () { - sandbox.restore(); - }); + // safeFrame implementation + const $sf = { + ext: { + geom: function() {} + } + }; + + beforeEach(() => { + window.ADAGIO = {}; + window.ADAGIO.adUnits = {}; + window.ADAGIO.pbjsAdUnits = []; + window.ADAGIO.queue = []; + window.ADAGIO.versions = {}; + window.ADAGIO.versions.adagioBidderAdapter = VERSION; + window.ADAGIO.pageviewId = 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a'; + + adagioMock = sinon.mock(adagio); + utilsMock = sinon.mock(utils); - const bid = { - 'bidder': 'adagio', - 'params': { - organizationId: '0', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }; + sandbox = sinon.sandbox.create(); + }); - const bidWithMediaTypes = { - 'bidder': 'adagio', - 'params': { - organizationId: '0', - placement: 'PAVE_ATF', - site: 'SITE-NAME', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf' - }, - 'adUnitCode': 'adunit-code-2', - 'mediaTypes': { - banner: { - sizes: [[300, 250]], - } - }, - sizes: [[300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } + afterEach(() => { + window.ADAGIO = undefined; - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); - }) + adagioMock.restore(); + utilsMock.restore(); - it('should compute a printNumber for the new bid request on same adUnitCode and same pageviewId', () => { - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits).ok; - expect(window.top.ADAGIO.adUnits['adunit-code']).ok; - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(2); + sandbox.restore(); + }); - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(3); + describe('isBidRequestValid()', function() { + it('should return true when required params have been found', function() { + const bid = new BidRequestBuilder().withParams().build(); - window.top.ADAGIO.pageviewId = 123; - spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when organization params is not passed', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.organizationId; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); - }); + it('should return false if bid.params is missing', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder().build(); - it('should return false when site params is not passed', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.site; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); + expect(spec.isBidRequestValid(bid01)).to.equal(false); + sinon.assert.callCount(utils.logWarn, 1); }); - it('should return false when placement params is not passed', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.placement; - expect(spec.isBidRequestValid(bidTest)).to.equal(false); - }); + it('should return false when a required param is missing', function() { + const bid01 = new BidRequestBuilder({ params: { + organizationId: '1000', + placement: 'PAVE_ATF' + }}).build(); - it('should add autodetected adUnitElementId and environment params', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.adUnitElementId; - delete bidTest.params.environment; - sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').returns({divId: 'banner-atf'}); - sandbox.stub(_features, 'getDevice').returns(4); - spec.isBidRequestValid(bidTest) - expect(bidTest.params.adUnitElementId).to.equal('banner-atf'); - expect(bidTest.params.environment).to.equal('mobile'); - }) + const bid02 = new BidRequestBuilder({ params: { + organizationId: '1000', + site: 'SITE-NAME' + }}).build(); - it('should add autodetected tablet environment params', () => { - const bidTest = utils.deepClone(bid); - delete bidTest.params.environment; - sandbox.stub(_features, 'getDevice').returns(5); - spec.isBidRequestValid(bidTest) - expect(bidTest.params.environment).to.equal('tablet'); - }) + const bid03 = new BidRequestBuilder({ params: { + placement: 'PAVE_ATF', + site: 'SITE-NAME' + }}).build(); + + expect(spec.isBidRequestValid(bid01)).to.equal(false); + expect(spec.isBidRequestValid(bid02)).to.equal(false); + expect(spec.isBidRequestValid(bid03)).to.equal(false); + }); + + it('should return false when refererInfo.reachedTop is false', function() { + sandbox.spy(utils, 'logWarn'); + sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: false }); + const bid = new BidRequestBuilder().withParams().build(); - it('should return false if not in the window.top', () => { - sandbox.stub(utils, 'getWindowTop').throws(); expect(spec.isBidRequestValid(bid)).to.equal(false); + sinon.assert.callCount(utils.logWarn, 1); + sinon.assert.calledWith(utils.logWarn, 'Adagio: the main page url is unreachabled.'); }); - it('should expose ADAGIO.pbjsAdUnits in window', () => { - spec.isBidRequestValid(bidWithMediaTypes); + it('should log warning and enqueue the bid object in ADAGIO.queue when isBidRequestValid is false', function() { + sandbox.stub(Date, 'now').returns(12345); + sandbox.spy(utils, 'logWarn'); + sandbox.spy(adagio, 'enqueue'); + + const bid = new BidRequestBuilder({'params': { + organizationId: '1000', + placement: 'PAVE_ATF' + }}).build(); + + const expectedEnqueued = { + action: 'pb-dbg', + ts: 12345, + data: { bid } + }; + spec.isBidRequestValid(bid); - expect(window.top.ADAGIO.pbjsAdUnits).ok; - expect(window.top.ADAGIO.pbjsAdUnits).to.have.lengthOf(2); - const adUnitWithMediaTypeSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code-2')[0]; - const adUnitWithSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code')[0]; - expect(adUnitWithMediaTypeSizes.sizes).to.eql([[300, 250]]); - expect(adUnitWithSizes.sizes).to.eql([[300, 250], [300, 600]]); + + sinon.assert.calledWith(adagio.enqueue, expectedEnqueued); + sinon.assert.callCount(utils.logWarn, 1); }); - }); - describe('buildRequests', () => { - const sandbox = sinon.createSandbox(); + describe('Store ADAGIO global in window.top or window.self depending on context', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + sizes: [[300, 250], [300, 600]] + }).withParams().build(); - const banner300x250 = { - x: 0, - y: 0, - width: 300, - height: 250, - getBoundingClientRect: () => { - return { - width: banner300x250.width, - height: banner300x250.height, - left: banner300x250.x, - top: banner300x250.y, - right: banner300x250.x + banner300x250.width, - bottom: banner300x250.y + banner300x250.height - }; - }, - }; + const bid02 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-02', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + }).withParams().build(); - const banner300x600 = { - x: 0, - y: 0, - width: 300, - height: 600, - getBoundingClientRect: () => { - return { - width: banner300x600.width, - height: banner300x600.height, - left: banner300x600.x, - top: banner300x600.y, - right: banner300x600.x + banner300x600.width, - bottom: banner300x600.y + banner300x600.height - }; - }, - }; + const bid03 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-02', + mediaTypes: { + banner: { sizes: [[300, 600]] } + }, + }).withParams().build(); + + const expected = [ + { + code: 'adunit-code-01', + sizes: [[300, 250], [300, 600]], + mediaTypes: {}, + bids: [{ + bidder: 'adagio', + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code', + environment: 'desktop' + } + }], + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + pageviewId: 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a', + printNumber: 1, + }, + { + code: 'adunit-code-02', + sizes: [[300, 600]], + mediaTypes: { + banner: { sizes: [[300, 600]] } + }, + bids: [{ + bidder: 'adagio', + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code', + environment: 'desktop' + } + }], + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + pageviewId: 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a', + printNumber: 2, + } + ]; - const computedStyleBlock = { - display: 'block' - }; + it('should store bids config once by bid in window.top if it accessible', function() { + sandbox.stub(adagio, 'getCurrentWindow').returns(window.top); - const computedStyleNone = { - display: 'none' - }; + // replace by the values defined in beforeEach + window.top.ADAGIO = { + ...window.ADAGIO + } - const stubs = { - topGetElementById: undefined, - topGetComputedStyle: undefined - } + spec.isBidRequestValid(bid01); + spec.isBidRequestValid(bid02); + spec.isBidRequestValid(bid03); - top.ADAGIO = top.ADAGIO || {}; - top.ADAGIO.adUnits = top.ADAGIO.adUnits || {}; - top.ADAGIO.pbjsAdUnits = top.ADAGIO.pbjsAdUnits || []; + expect(find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01')).to.deep.eql(expected[0]); + expect(find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-02')).to.deep.eql(expected[1]); + }); - beforeEach(function () { - stubs.topGetElementById = sandbox.stub(top.document, 'getElementById'); - stubs.topGetComputedStyle = sandbox.stub(top, 'getComputedStyle'); + it('should store bids config once by bid in current window', function() { + sandbox.stub(adagio, 'getCurrentWindow').returns(window.self); - stubs.topGetElementById.withArgs('banner-atf-123').returns(banner300x250); - stubs.topGetElementById.withArgs('banner-atf-456').returns(banner300x600); - stubs.topGetElementById.withArgs('does-not-exist').returns(null); - stubs.topGetComputedStyle.returns(computedStyleBlock); - }); + spec.isBidRequestValid(bid01); + spec.isBidRequestValid(bid02); + spec.isBidRequestValid(bid03); - afterEach(function () { - sandbox.restore(); + expect(find(window.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01')).to.deep.eql(expected[0]); + expect(find(window.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-02')).to.deep.eql(expected[1]); + }); }); + }); - after(function() { - sandbox.reset(); - }) - - let bidRequests = [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '123', - site: 'ADAGIO-123', - placement: 'PAVE_ATF-123', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-123' - }, - 'adUnitCode': 'adunit-code1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }, - { - 'bidder': 'adagio', - 'params': { - organizationId: '123', - site: 'ADAGIO-123', - placement: 'PAVE_ATF-123', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-123' - }, - 'adUnitCode': 'adunit-code2', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - }, - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-456' - }, - 'adUnitCode': 'adunit-code3', - 'mediaTypes': { - banner: { - sizes: [[300, 250]] - } - }, - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } + describe('buildRequests()', function() { + const expectedDataKeys = [ + 'id', + 'organizationId', + 'secure', + 'device', + 'site', + 'pageviewId', + 'adUnits', + 'gdpr', + 'schain', + 'prebidVersion', + 'adapterVersion', + 'featuresVersion' ]; - const bidRequestsWithPostBid = [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - pagetype: 'ARTICLE', - adUnitElementId: 'banner-atf-456', - postBid: true - }, - 'adUnitCode': 'adunit-code3', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - } - ]; + it('groups requests by organizationId', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bid02 = new BidRequestBuilder().withParams().build(); + const bid03 = new BidRequestBuilder().withParams({ + organizationId: '1002' + }).build(); - let consentString = 'theConsentString'; - let bidderRequest = { - 'bidderCode': 'adagio', - 'auctionId': '12jejebn', - 'bidderRequestId': 'hehehehbeheh', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - allowAuctionWithoutConsent: true, - apiVersion: 1, - }, - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'http://test.io/index.html?pbjs_debug=true' - } - }; + const bidderRequest = new BidderRequestBuilder().build(); - let bidderRequestTCF2 = { - 'bidderCode': 'adagio', - 'auctionId': '12jejebn', - 'bidderRequestId': 'hehehehbeheh', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - vendorData: { - tcString: consentString, - gdprApplies: true - }, - gdprApplies: true, - apiVersion: 2 - }, - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'http://test.io/index.html?pbjs_debug=true' - } - }; + const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest); - it('groups requests by siteId', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); - expect(requests[0].data.organizationId).to.equal('123'); + expect(requests[0].data.organizationId).to.equal('1000'); expect(requests[0].data.adUnits).to.have.lengthOf(2); - expect(requests[1].data.organizationId).to.equal('456'); + expect(requests[1].data.organizationId).to.equal('1002'); expect(requests[1].data.adUnits).to.have.lengthOf(1); }); - it('sends bid request to ENDPOINT_PB via POST', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT); - expect(request.data.prebidVersion).to.equal('$prebid.version$'); - }); + it('should send bid request to ENDPOINT_PB via POST', function() { + sandbox.stub(adagio, 'getDevice').returns({ a: 'a' }); + sandbox.stub(adagio, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); + sandbox.stub(adagio, 'getPageviewId').returns('1234-567'); + sandbox.stub(adagio, 'getFeatures').returns({}); - it('features params "adunit_position" must be empty if adUnitElement is not found in the DOM', () => { - const requests = spec.buildRequests([Object.assign({}, bidRequests[0], {params: {adUnitElementId: 'does-not-exist'}})], bidderRequest); - const request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.adunit_position).to.deep.equal(''); - }); + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); - it('features params "adunit_position" should be computed even if DOM element is display:none', () => { - stubs.topGetComputedStyle.returns(computedStyleNone); - const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.adunit_position).to.equal('0x0'); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - it('features params "viewport" should be computed even if window.innerWidth is not supported', () => { - sandbox.stub(top, 'innerWidth').value(undefined); - const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].features.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].options.contentType).to.eq('text/plain'); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); }); - it('AdUnit requested should have the correct sizes array depending on the config', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests[1].data.adUnits[0]).to.have.property('mediaTypes'); - }); + it('should enqueue computed features for collect usage', function() { + sandbox.stub(Date, 'now').returns(12345); - it('features params must be an object if featurejs is loaded', () => { - let requests = spec.buildRequests(bidRequests, bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - }); + for (const prop in _features) { + sandbox.stub(_features, prop).returns(''); + } - it('outerAdUnitElementId must be added when PostBid param has been set', () => { - top.ADAGIO = top.ADAGIO || {}; - top.ADAGIO.pbjsAdUnits = []; + adagioMock.expects('enqueue').withExactArgs({ + action: 'features', + ts: 12345, + data: { + 'gpt-adunit-code': { + features: {}, + version: '1' + } + } + }).atLeast(1); - top.ADAGIO.pbjsAdUnits.push({ - code: bidRequestsWithPostBid[0].adUnitCode, - sizes: bidRequestsWithPostBid[0].sizes, - bids: [{ - bidder: bidRequestsWithPostBid[0].bidder, - params: bidRequestsWithPostBid[0].params - }] - }); - let requests = spec.buildRequests(bidRequestsWithPostBid, bidderRequest); - let request = requests[0]; - expect(request.data.adUnits[0].features).to.exist; - expect(request.data.adUnits[0].params.outerAdUnitElementId).to.exist; - top.ADAGIO.pbjsAdUnits = undefined; - }); + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); - it('generates a pageviewId if missing', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - delete window.top.ADAGIO.pageviewId; + const requests = spec.buildRequests([bid01], bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); - expect(requests[0].data.pageviewId).to.exist.and.to.not.equal('_').and.to.not.equal(''); - expect(requests[0].data.pageviewId).to.equal(requests[1].data.pageviewId); + adagioMock.verify(); }); - it('uses an existing pageviewId if present', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - window.top.ADAGIO.pageviewId = 'abc'; + it('should filter some props in case refererDetection.reachedTop is false', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder({ + refererInfo: { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + null, + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + } + }).build(); - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); + const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.pageviewId).to.equal('abc'); - expect(requests[1].data.pageviewId).to.equal('abc'); + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].features).to.exist; + expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); - it('should send the printNumber in features object', () => { - window.top.ADAGIO = window.top.ADAGIO || {}; - window.top.ADAGIO.pageviewId = 'abc'; - window.top.ADAGIO.adUnits['adunit-code1'] = { - pageviewId: 'abc', - printNumber: 2 + describe('with sChain', function() { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'ssp.test', + sid: '00001', + hp: 1 + }] }; - const requests = spec.buildRequests([bidRequests[0]], bidderRequest); - const request = requests[0]; - expect(request.data.adUnits[0].features.print_number).to.equal('2'); - }); - it('organizationId param key must be a string', () => { - const requests = spec.buildRequests([Object.assign({}, bidRequests[0], {params: {organizationId: 1010}})], bidderRequest); - const request = requests[0]; - expect(request.data.adUnits[0].params).to.exist; - expect(request.data.adUnits[0].params.organizationId).to.deep.equal('1010'); - expect(request.data.organizationId).to.exist; - expect(request.data.organizationId).to.deep.equal('1010'); - }); + it('should add the schain if available at bidder level', function() { + const bid01 = new BidRequestBuilder({ schain }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); - it('GDPR consent is applied', () => { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(1); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(1); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - it('GDPR consent is applied w/ TCF2', () => { - const requests = spec.buildRequests(bidRequests, bidderRequestTCF2); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(1); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(2); - }); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.schain).to.deep.equal(schain); + }); - it('GDPR consent is not applied', () => { - bidderRequest.gdprConsent.gdprApplies = false; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(0); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(1); - }); + it('Schain should not be added to the request', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); - it('GDPR consent is not applied w/ TCF2', () => { - bidderRequestTCF2.gdprConsent.gdprApplies = false; - const requests = spec.buildRequests(bidRequests, bidderRequestTCF2); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); - expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(0); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(2); - }); - - it('GDPR consent is undefined', () => { - delete bidderRequest.gdprConsent.consentString; - delete bidderRequest.gdprConsent.gdprApplies; - delete bidderRequest.gdprConsent.allowAuctionWithoutConsent; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.not.have.property('consentString'); - expect(request.data.gdpr).to.not.have.property('gdprApplies'); - expect(request.data.gdpr).to.not.have.property('allowAuctionWithoutConsent'); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(1); - }); - - it('GDPR consent is undefined w/ TCF2', () => { - delete bidderRequestTCF2.gdprConsent.consentString; - delete bidderRequestTCF2.gdprConsent.gdprApplies; - delete bidderRequestTCF2.gdprConsent.vendorData; - const requests = spec.buildRequests(bidRequests, bidderRequestTCF2); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.not.have.property('consentString'); - expect(request.data.gdpr).to.not.have.property('gdprApplies'); - expect(request.data.gdpr.apiVersion).to.exist.and.to.equal(2); - }); + const requests = spec.buildRequests([bid01], bidderRequest); - it('GDPR consent bidderRequest does not have gdprConsent', () => { - delete bidderRequest.gdprConsent; - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests).to.have.lengthOf(2); - const request = requests[0]; - expect(request.data.gdpr).to.exist; - expect(request.data.gdpr).to.be.empty; + expect(requests[0].data.schain).to.not.exist; + }); }); - it('should expose version in window', () => { - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.versions).ok; - expect(window.top.ADAGIO.versions.adagioBidderAdapter).to.eq(VERSION); - }); + describe('with GDPR', function() { + const bid01 = new BidRequestBuilder().withParams().build(); - it('should returns an empty array if the bidder cannot access to window top (based on refererInfo.reachedTop)', () => { - const requests = spec.buildRequests(bidRequests, { - ...bidderRequest, - refererInfo: { reachedTop: false } - }); - expect(requests).to.be.empty; - }); + const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; - it('Should add the schain if available at bidder level', () => { - const bidRequest = Object.assign({}, bidRequests[0], { - schain: { - ver: '1.0', - complete: 1, - nodes: [{ - asi: 'ssp.test', - sid: '00001', - hp: 1 - }] - } - }); + const gdprConsentBuilderTCF1 = function gdprConsentBuilderTCF1(applies, allows) { + return { + consentString, + gdprApplies: applies, + allowAuctionWithoutConsent: allows, + apiVersion: 1 + }; + }; - const requests = spec.buildRequests([bidRequest], bidderRequest); - const request = requests[0]; + const gdprConsentBuilderTCF2 = function gdprConsentBuilderTCF2(applies) { + return { + consentString, + gdprApplies: applies, + apiVersion: 2 + }; + }; - expect(request.data.schain).to.exist; - expect(request.data.schain).to.deep.equal({ - ver: '1.0', - complete: 1, - nodes: [{ - asi: 'ssp.test', - sid: '00001', - hp: 1 - }] + context('When GDPR applies', function() { + it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF1(true, true) + }).build(); + + const expected = { + consentString, + allowAuctionWithoutConsent: 1, + consentRequired: 1, + apiVersion: 1 + }; + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); + + it('send data.gdpr object to the server from TCF v.2 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF2(true) + }).build(); + + const expected = { + consentString, + consentRequired: 1, + apiVersion: 2 + }; + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); }); - }); - it('Schain should not be added to the request', () => { - const requests = spec.buildRequests([bidRequests[0]], bidderRequest); - const request = requests[0]; - expect(request.data.schain).to.not.exist; - }); - }); + context('When GDPR does not applies', function() { + it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF1(false, true) + }).build(); + + const expected = { + consentString, + allowAuctionWithoutConsent: 1, + consentRequired: 0, + apiVersion: 1 + }; - describe('interpretResponse', () => { - const sandbox = sinon.createSandbox(); + const requests = spec.buildRequests([bid01], bidderRequest); - let serverResponse = { - body: { - data: { - pred: 1 - }, - bids: [ - { - ad: '
', - cpm: 1, - creativeId: 'creativeId', - currency: 'EUR', - height: 250, - netRevenue: true, - requestId: 'c180kg4267tyqz', - ttl: 360, - width: 300 - } - ] - } - }; + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); - let emptyBodyServerResponse = { - body: null - }; + it('send data.gdpr object to the server from TCF v.2 cmp', function() { + const bidderRequest = new BidderRequestBuilder({ + gdprConsent: gdprConsentBuilderTCF2(false) + }).build(); - let withoutBidsArrayServerResponse = { - body: { - bids: [] - } - }; + const expected = { + consentString, + consentRequired: 0, + apiVersion: 2 + }; - let serverResponseWhichThrowsException = { + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.gdpr).to.deep.equal(expected); + }); + }); + + context('When GDPR is undefined in bidderRequest', function() { + it('send an empty data.gdpr to the server', function() { + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.gdpr).to.be.empty; + }); + }); + }); + }); + + describe('interpretResponse()', function() { + let serverResponse = { body: { data: { pred: 1 }, - bids: { - foo: 'bar' - } + bids: [{ + ad: '
', + cpm: 1, + creativeId: 'creativeId', + currency: 'EUR', + height: 250, + netRevenue: true, + requestId: 'c180kg4267tyqz', + ttl: 360, + width: 300 + }] } }; let bidRequest = { - 'data': { - 'adUnits': [ - { - 'bidder': 'adagio', - 'params': { - organizationId: '456', - site: 'ADAGIO-456', - placement: 'PAVE_ATF-456', - adUnitElementId: 'banner-atf-456', - pagetype: 'ARTICLE', - category: 'NEWS', - subcategory: 'SPORT', - environment: 'SITE-MOBILE' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': 'c180kg4267tyqz', - 'bidderRequestId': '8vfscuixrovn8i', - 'auctionId': 'lel4fhp239i9km', - 'pageviewId': 'd8c4fl2k39i0wn', - } - ] + data: { + adUnits: [{ + bidder: 'adagio', + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + adUnitElementId: 'gpt-adunit-code', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'desktop' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { sizes: [[300, 250], [300, 600]] } + }, + bidId: 'c180kg4267tyqz', + bidderRequestId: '8vfscuixrovn8i', + auctionId: 'lel4fhp239i9km', + pageviewId: 'd8c4fl2k39i0wn', + }] } }; - afterEach(function() { - sandbox.restore(); - }); + it('should return an empty response array if body is empty', function() { + expect(spec.interpretResponse({ + body: null + }, bidRequest)).to.be.an('array').length(0); - it('Should returns empty response if body is empty', () => { - expect(spec.interpretResponse(emptyBodyServerResponse, bidRequest)).to.be.an('array').length(0); - expect(spec.interpretResponse({body: {}}, bidRequest)).to.be.an('array').length(0); + expect(spec.interpretResponse({ + body: {} + }, bidRequest)).to.be.an('array').length(0); }); - it('Should returns empty response if bids array is empty', () => { - expect(spec.interpretResponse({withoutBidsArrayServerResponse}, bidRequest)).to.be.an('array').length(0); + it('should return an empty response array if bids array is empty', function() { + expect(spec.interpretResponse({ + body: { + bids: [] + } + }, bidRequest)).to.be.an('array').length(0); }); - it('should get correct bid response', () => { + it('should handle properly a correct bid response', function() { let expectedResponse = [{ ad: '
', cpm: 1, @@ -683,87 +591,512 @@ describe('adagioAdapter', () => { requestId: 'c180kg4267tyqz', ttl: 360, width: 300, - placement: 'PAVE_ATF-456', - site: 'ADAGIO-456', + placement: 'PAVE_ATF', + site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', - environment: 'SITE-MOBILE' + environment: 'desktop' }]; + expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); }); - it('Should populate ADAGIO queue with ssp-data', () => { - spec.interpretResponse(serverResponse, bidRequest); - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.queue).to.be.an('array'); - }); + it('should populate ADAGIO queue with ssp-data', function() { + sandbox.stub(Date, 'now').returns(12345); + + adagioMock.expects('enqueue').withExactArgs({ + action: 'ssp-data', + ts: 12345, + data: serverResponse.body.data + }).once(); - it('Should not populate ADAGIO queue with ssp-data if not in top window', () => { - utils.getWindowTop().ADAGIO.queue = []; - sandbox.stub(utils, 'getWindowTop').throws(); spec.interpretResponse(serverResponse, bidRequest); - expect(window.top.ADAGIO).ok; - expect(window.top.ADAGIO.queue).to.be.an('array'); - expect(window.top.ADAGIO.queue).empty; + + adagioMock.verify(); }); - it('should return an empty response even if an exception is ', () => { - expect(spec.interpretResponse(serverResponseWhichThrowsException, bidRequest)).to.be.an('array').length(0); + it('should properly try-catch an exception and return an empty array', function() { + sandbox.stub(adagio, 'enqueue').throws(); + utilsMock.expects('logError').once(); + + expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array').length(0); + + utilsMock.verify(); }); }); - describe('getUserSyncs', () => { + describe('getUserSyncs()', function() { const syncOptions = { - 'iframeEnabled': 'true' - } - const serverResponses = [ - { + syncEnabled: false + }; + + it('should handle user syncs if data is in the server response ', function() { + const serverResponses = [{ body: { userSyncs: [ - { - t: 'i', - u: 'https://test.url.com/setuid' - }, - { - t: 'p', - u: 'https://test.url.com/setuid' - } + { t: 'i', u: 'https://test.url.com/setuid' }, + { t: 'p', u: 'https://test.url.com/setuid' } ] } - } - ]; - - const emptyServerResponses = [ - { - body: '' - } - ]; + }]; - it('should handle correctly user syncs', () => { let result = spec.getUserSyncs(syncOptions, serverResponses); - let emptyResult = spec.getUserSyncs(syncOptions, emptyServerResponses); + expect(result[0].type).to.equal('iframe'); expect(result[0].url).contain('setuid'); + expect(result[1].type).to.equal('image'); - expect(emptyResult).to.equal(false); + expect(result[1].url).contain('setuid'); + }); + + it('should return false if data is not in server response', function() { + const serverResponse = [{ body: '' }]; + const result = spec.getUserSyncs(syncOptions, serverResponse); + expect(result).to.equal(false); }); }); - describe('adagioScriptFromLocalStorageCb', () => { + describe('Adagio features', function() { + it('should return all expected features when all expected bidder params are available', function() { + sandbox.stub(window.top.document, 'getElementById').returns( + fixtures.getElementById() + ); + sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); + + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const result = adagio.getFeatures(bidRequest, bidderRequest); + + expect(result.adunit_position).to.match(/^[\d]+x[\d]+$/); + expect(result.page_dimensions).to.match(/^[\d]+x[\d]+$/); + expect(result.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.url).to.be.a('String'); + expect(result.device).to.be.a('String'); + expect(result.os).to.be.a('String'); + expect(result.browser).to.be.a('String'); + }); + + it('should return all expected features when `adUnitElementId` param is not available', function() { + const bidRequest = new BidRequestBuilder({ + params: { + organizationId: '1000', + placement: 'PAVE_ATF', + site: 'SITE-NAME' + }, + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const result = adagio.getFeatures(bidRequest, bidderRequest); + + expect(result.adunit_position).to.not.exist; + expect(result.page_dimensions).to.be.a('String'); + expect(result.viewport_dimensions).to.be.a('String'); + expect(result.print_number).to.be.a('String'); + expect(result.dom_loading).to.be.a('String'); + expect(result.user_timestamp).to.be.a('String'); + expect(result.url).to.be.a('String'); + expect(result.device).to.be.a('String'); + expect(result.os).to.be.a('String'); + expect(result.browser).to.be.a('String'); + }); + + it('should not return feature with an empty value', function() { + sandbox.stub(_features, 'getDomLoadingDuration').returns(''); + sandbox.stub(_features, 'getUrl').returns(''); + sandbox.stub(_features, 'getBrowser').returns(''); + + const bidRequest = new BidRequestBuilder({ + 'mediaTypes': { + banner: { sizes: [[300, 250]] } + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const result = adagio.getFeatures(bidRequest, bidderRequest); + + expect(result.adunit_position).to.not.exist; + expect(result.page_dimensions).to.exist; + expect(result.viewport_dimensions).to.exist; + expect(result.print_number).to.exist; + expect(result.dom_loading).to.not.exist; + expect(result.user_timestamp).to.exist; + expect(result.url).to.not.exist; + expect(result.device).to.exist; + expect(result.os).to.exist; + expect(result.browser).to.not.exist; + }); + + describe('getPageDimensions feature', function() { + afterEach(() => { + delete window.$sf; + }); + + it('should not compute the page dimensions in cross-origin iframe', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + const result = _features.getPageDimensions(); + expect(result).to.eq(''); + }); + + it('should not compute the page dimensions even with safeFrame api', function() { + window.$sf = $sf; + const result = _features.getPageDimensions(); + expect(result).to.eq(''); + }); + + it('should not compute the page dimensions if is not in the DOM', function() { + sandbox.stub(window.top.document, 'querySelector').withArgs('body').returns(null); + const result = _features.getPageDimensions(); + expect(result).to.eq(''); + }); + + it('should compute the page dimensions based on body and viewport dimensions', function() { + sandbox.stub(window.top.document, 'querySelector').withArgs('body').returns({ scrollWidth: 1360, offsetWidth: 1280, scrollHeight: 2000, offsetHeight: 1000 }); + const result = _features.getPageDimensions(); + expect(result).to.eq('1360x2000'); + }); + }); + + describe('getViewPortDimensions feature', function() { + afterEach(() => { + delete window.$sf; + }); + + it('should not compute the viewport dimensions in cross-origin iframe', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + const result = _features.getViewPortDimensions(); + expect(result).to.eq(''); + }); + + it('should compute the viewport dimensions in cross-origin iframe w/ safeFrame api', function() { + window.$sf = $sf; + sandbox.stub(window.$sf.ext, 'geom').returns({ + win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, + self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq('1920x1177'); + }); + + it('should not compute the viewport dimensions if safeFrame api is misimplemented', function() { + window.$sf = { + ext: { geom: 'nothing' } + }; + const result = _features.getViewPortDimensions(); + expect(result).to.eq(''); + }); + + it('should not compute the viewport dimensions if is not in the DOM', function() { + const querySelectorSpy = sandbox.spy(() => null); + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'https://mytest.io' }, + document: { querySelector: querySelectorSpy } + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq(''); + }); + + it('should compute the viewport dimensions based on window', function() { + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'https://mytest.io' }, + innerWidth: 960, + innerHeight: 3000 + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq('960x3000'); + }); + + it('should compute the viewport dimensions based on body', function() { + const querySelectorSpy = sandbox.spy(() => ({ clientWidth: 1024, clientHeight: 2000 })); + sandbox.stub(utils, 'getWindowTop').returns({ + location: { href: 'https://mytest.io' }, + document: { querySelector: querySelectorSpy } + }); + const result = _features.getViewPortDimensions(); + expect(result).to.eq('1024x2000'); + }); + }); + + describe('getSlotPosition feature', function() { + let getElementByIdStub; + let getComputedStyleStub; + + beforeEach(() => { + getElementByIdStub = sandbox.stub(window.top.document, 'getElementById'); + getElementByIdStub.returns(fixtures.getElementById()); + getComputedStyleStub = sandbox.stub(window.top, 'getComputedStyle'); + getComputedStyleStub.returns({ display: 'block' }); + }); + + afterEach(() => { + delete window.$sf; + getElementByIdStub.restore(); + getComputedStyleStub.restore(); + }); + + it('should not compute the slot position in cross-origin iframe', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq(''); + }); + + it('should compute the slot position in cross-origin iframe w/ safeFrame api', function() { + window.$sf = $sf; + sandbox.stub(window.$sf.ext, 'geom').returns({ + win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, + self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, + }); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq('210x859'); + }); + + it('should not compute the slot position if safeFrame api is misimplemented', function() { + window.$sf = { + ext: { geom: 'nothing' } + }; + utilsMock.expects('logWarn').once(); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq(''); + utilsMock.verify(); + }); + + it('should not compute the slot position due to unreachable adUnitElementId', function() { + getElementByIdStub.returns(null); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq(''); + }); + + it('should use a quick switch to display slot and compute position', function() { + getComputedStyleStub.returns({ display: 'none' }); + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq('800x300'); + }); + + it('should compute the slot position based on window.top w/o postBid param', function() { + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: false }); + expect(result).to.eq('800x300'); + }); + + it.skip('should compute the slot position inside the parent window (window.top) when safeFrame is not available and postBid params is `true`', function() { + const result = _features.getSlotPosition({ adUnitElementId: 'gpt-adunit-code', postBid: true }); + // expect(result).to.eq('800x300'); + }); + }); + }); + + describe('optional params auto detection', function() { + it('should auto detect environment', function() { + const getDeviceStub = sandbox.stub(_features, 'getDevice'); + + getDeviceStub.returns(5); + expect(adagio.autoDetectEnvironment()).to.eq('tablet'); + + getDeviceStub.returns(4); + expect(adagio.autoDetectEnvironment()).to.eq('mobile'); + + getDeviceStub.returns(2); + expect(adagio.autoDetectEnvironment()).to.eq('desktop'); + }); + + it('should auto detect adUnitElementId when GPT is used', function() { + sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); + expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); + }); + }); + + describe('print number handling', function() { + it('should return 1 if no adunit-code found. This means it is the first auction', function() { + sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); + expect(adagio.computePrintNumber('adunit-code')).to.eql(1); + }); + + it('should increment the adunit print number when the adunit-code has already been used for an other auction', function() { + sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); + + window.top.ADAGIO.adUnits['adunit-code'] = { + pageviewId: 'abc-def', + printNumber: 1, + }; + + expect(adagio.computePrintNumber('adunit-code')).to.eql(2); + }); + }); + + describe('site information using refererDetection or window.top', function() { + it('should returns domain, page and window.referrer in a window.top context', function() { + sandbox.stub(utils, 'getWindowTop').returns({ + location: { + hostname: 'test.io', + href: 'https://test.io/article/a.html' + }, + document: { + referrer: 'https://google.com' + } + }); + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'test.io', + page: 'https://test.io/article/a.html', + referrer: 'https://google.com' + }); + }); + + it('should returns domain and page in a cross-domain w/ top domain reached context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 0, + reachedTop: true, + referer: 'http://level.io/', + stack: [ + 'http://level.io/', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'level.io', + page: 'http://level.io/', + referrer: '' + }); + }); + + it('should not return anything in a cross-domain w/o top domain reached and w/o ancestor context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + null, + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: '', + page: '', + referrer: '' + }); + }); + + it('should return domain only in a cross-domain w/o top domain reached and w/ ancestors context', function() { + sandbox.stub(utils, 'getWindowTop').throws(); + + const info = { + numIframes: 2, + reachedTop: false, + referer: 'http://example.com/iframe1.html', + stack: [ + 'http://mytest.com/', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: '' + }; + + const bidderRequest = new BidderRequestBuilder({ + refererInfo: info + }).build(); + + expect(adagio.getSite(bidderRequest)).to.deep.equal({ + domain: 'mytest.com', + page: '', + referrer: '' + }); + }); + }); + + describe('adagioScriptFromLocalStorageCb()', function() { const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I='; const INVALID_HASH = 'invalid'; const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n'; const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; + beforeEach(function() { + localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + }); + + describe('getAdagioScript', function() { + it('should run storage.getDataFromLocalStorage callback and call adagioScriptFromLocalStorageCb() ', function() { + sandbox.spy(adagio, 'adagioScriptFromLocalStorageCb'); + const getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsArg(1); + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + + getAdagioScript(); + + sinon.assert.callCount(getDataFromLocalStorageStub, 1); + sinon.assert.callCount(adagio.adagioScriptFromLocalStorageCb, 1); + }); + + it('should load external script if the user consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true); + getAdagioScript(); + + expect(loadExternalScript.called).to.be.true; + }); + + it('should not load external script if the user does not consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); + getAdagioScript(); + + expect(loadExternalScript.called).to.be.false; + }); + + it('should remove the localStorage key if exists and the user does not consent', function() { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, 'the script'); + + getAdagioScript(); + + expect(loadExternalScript.called).to.be.false; + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + }) + }); + it('should verify valid hash with valid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').once(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script.').once(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); @@ -774,9 +1107,9 @@ describe('adagioAdapter', () => { it('should verify valid hash with invalid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + INVALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); @@ -787,9 +1120,9 @@ describe('adagioAdapter', () => { it('should verify invalid hash with valid script', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + INVALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); @@ -800,14 +1133,21 @@ describe('adagioAdapter', () => { it('should verify missing hash', function () { localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT); - utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').once(); - utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); + utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').once(); + utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; utilsMock.verify(); }); + + it('should return false if content script does not exist in localStorage', function() { + sandbox.spy(utils, 'logWarn'); + expect(adagioScriptFromLocalStorageCb(null)).to.be.undefined; + sinon.assert.callCount(utils.logWarn, 1); + sinon.assert.calledWith(utils.logWarn, 'Adagio: script not found.'); + }); }); }); diff --git a/test/spec/modules/adbutlerBidAdapter_spec.js b/test/spec/modules/adbutlerBidAdapter_spec.js index a9b56ade79e..6dedce321d8 100644 --- a/test/spec/modules/adbutlerBidAdapter_spec.js +++ b/test/spec/modules/adbutlerBidAdapter_spec.js @@ -13,7 +13,10 @@ describe('AdButler adapter', function () { zoneID: '210093', keyword: 'red', minCPM: '1.00', - maxCPM: '5.00' + maxCPM: '5.00', + extra: { + foo: 'bar', + } }, placementCode: '/19968336/header-bid-tag-1', mediaTypes: { @@ -93,6 +96,13 @@ describe('AdButler adapter', function () { expect(requestURL).to.have.string(';kw=red;'); }); + it('should set the extra parameter', () => { + let requests = spec.buildRequests(bidRequests); + let requestURL = requests[0].url; + + expect(requestURL).to.have.string(';foo=bar;'); + }); + it('should increment the count for the same zone', function () { let bidRequests = [ { diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index def504b0424..0743ddd211d 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -71,40 +71,46 @@ describe('AdheseAdapter', function () { } }; + it('should include requested slots', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).slots).to.deep.include({ 'slotname': '_main_page_-leaderboard' }); + }); + it('should include all extra bid params', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'ag': [ '25' ] }); }); it('should include duplicate bid params once', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }), bidWithParams({ 'ag': '25', 'ci': 'gent' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25/cigent'); + expect(JSON.parse(req.data).parameters).to.deep.include({'ag': ['25']}).and.to.deep.include({ 'ci': [ 'gent' ] }); }); it('should split multiple target values', function () { let req = spec.buildRequests([ bidWithParams({ 'ci': 'london' }), bidWithParams({ 'ci': 'gent' }) ], bidderRequest); - expect(req.url).to.contain('/sl_main_page_-leaderboard/cilondon;gent'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'ci': [ 'london', 'gent' ] }); }); it('should include gdpr consent param', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(req.url).to.contain('/xtCONSENT_STRING'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xt': [ 'CONSENT_STRING' ] }); }); it('should include referer param in base64url format', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(req.url).to.contain('/xfaHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xf': [ 'aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ' ] }); }); it('should include id5 id as /x5 param', function () { - let req = spec.buildRequests([ bidWithParams({}, {'id5id': 'ID5-1234567890'}) ], bidderRequest); + let req = spec.buildRequests([ bidWithParams({}, { 'id5id': 'ID5-1234567890' }) ], bidderRequest); - expect(req.url).to.contain('/x5ID5-1234567890'); + expect(JSON.parse(req.data).parameters).to.deep.include({ 'x5': [ 'ID5-1234567890' ] }); }); it('should include bids', function () { @@ -113,6 +119,18 @@ describe('AdheseAdapter', function () { expect(req.bids).to.deep.equal([ bid ]); }); + + it('should make a POST request', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.method).to.equal('POST'); + }); + + it('should request the json endpoint', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.url).to.equal('https://ads-demo.adhese.com/json'); + }); }); describe('interpretResponse', () => { @@ -211,7 +229,8 @@ describe('AdheseAdapter', function () { adhese: { origin: 'RUBICON', originInstance: '', - originData: {} } + originData: {} + } }]; expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 71637781f24..87504aa46af 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -171,7 +171,8 @@ describe('Adkernel adapter', function () { nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', adm: '', w: 300, - h: 250 + h: 250, + dealid: 'deal' }] }], cur: 'USD', @@ -229,13 +230,22 @@ describe('Adkernel adapter', function () { cur: 'USD' }; + var sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}, timeout: 3000, bidderCode: 'adkernel'}); } const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST, dnt = true) { - let dntmock = sinon.stub(utils, 'getDNT').callsFake(() => dnt); + let dntmock = sandbox.stub(utils, 'getDNT').callsFake(() => dnt); let pbRequests = spec.buildRequests(bidRequests, bidderRequest); dntmock.restore(); let rtbRequests = pbRequests.map(r => JSON.parse(r.data)); @@ -307,6 +317,7 @@ describe('Adkernel adapter', function () { it('should fill device with caller macro', function () { expect(bidRequest).to.have.property('device'); expect(bidRequest.device).to.have.property('ip', 'caller'); + expect(bidRequest.device).to.have.property('ipv6', 'caller'); expect(bidRequest.device).to.have.property('ua', 'caller'); expect(bidRequest.device).to.have.property('dnt', 1); }); @@ -495,6 +506,7 @@ describe('Adkernel adapter', function () { expect(resp).to.have.property('ttl'); expect(resp).to.have.property('mediaType', BANNER); expect(resp).to.have.property('ad'); + expect(resp).to.have.property('dealId', 'deal'); expect(resp.ad).to.have.string(''); }); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 3508a1175a6..fe05634baae 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -55,7 +55,7 @@ describe('AdprimebBidAdapter', function () { expect(data.gdpr).to.not.exist; expect(data.ccpa).to.not.exist; let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'keywords'); + expect(placement).to.have.keys('placementId', 'bidId', 'identeties', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'keywords'); expect(placement.placementId).to.equal(0); expect(placement.bidId).to.equal('23fhj33i987f'); expect(placement.traffic).to.equal(BANNER); @@ -106,6 +106,23 @@ describe('AdprimebBidAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + describe('buildRequests with user ids', function () { + bid.userId = {} + bid.userId.idl_env = 'idl_env123'; + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Return bids with user identeties', function () { + let data = serverRequest.data; + let placements = data['placements']; + expect(data).to.be.an('object'); + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.property('identeties') + expect(placement.identeties).to.be.an('object') + expect(placement.identeties).to.have.property('identityLink') + expect(placement.identeties.identityLink).to.be.equal('idl_env123') + } + }); + }); describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { diff --git a/test/spec/modules/apstreamBidAdapter_spec.js b/test/spec/modules/apstreamBidAdapter_spec.js new file mode 100644 index 00000000000..c6546a3bd83 --- /dev/null +++ b/test/spec/modules/apstreamBidAdapter_spec.js @@ -0,0 +1,324 @@ +// jshint esversion: 6, es3: false, node: true +import {assert, expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/apstreamBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const validBidRequests = [{ + bidId: 'bidId', + adUnitCode: '/id/site1/header-ad', + sizes: [[980, 120], [980, 180]], + mediaTypes: { + banner: { + sizes: [[980, 120], [980, 180]] + } + }, + params: { + publisherId: '1234' + } +}]; + +describe('AP Stream adapter', function() { + describe('isBidRequestValid', function() { + const bid = { + bidder: 'apstream', + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + params: { + } + }; + + it('should return true when publisherId is configured and one media type', function() { + bid.params.publisherId = '1234'; + assert(spec.isBidRequestValid(bid)) + }); + + it('should return false when publisherId is configured and two media types', function() { + bid.mediaTypes.video = {sizes: [300, 250]}; + assert.isFalse(spec.isBidRequestValid(bid)) + }); + }); + + describe('buildRequests', function() { + it('should send request with correct structure', function() { + const request = spec.buildRequests(validBidRequests, { })[0]; + + assert.equal(request.method, 'GET'); + assert.deepEqual(request.options, {withCredentials: false}); + assert.ok(request.data); + }); + + it('should send request with different endpoints', function() { + const validTwoBidRequests = [ + ...validBidRequests, + ...[{ + bidId: 'bidId2', + adUnitCode: '/id/site1/header-ad', + sizes: [[980, 980], [980, 900]], + mediaTypes: { + banner: { + sizes: [[980, 980], [980, 900]] + } + }, + params: { + publisherId: '1234', + endpoint: 'site2.com' + } + }] + ]; + + const request = spec.buildRequests(validTwoBidRequests, {}); + assert.isArray(request); + assert.lengthOf(request, 2); + assert.equal(request[1].data.bids, 'bidId2:t=b,s=980x980_980x900,c=/id/site1/header-ad'); + }); + + it('should send request with adUnit code', function() { + const adunitCodeValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + params: { + code: 'Site1_Leaderboard' + } + } + } + ]; + + const request = spec.buildRequests(adunitCodeValidBidRequests, { })[0]; + assert.equal(request.data.bids, 'bidId:t=b,s=980x120_980x180,c=Site1_Leaderboard'); + }); + + it('should send request with adUnit id', function() { + const adunitIdValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + params: { + adunitId: '12345' + } + } + } + ]; + + const request = spec.buildRequests(adunitIdValidBidRequests, { })[0]; + assert.equal(request.data.bids, 'bidId:t=b,s=980x120_980x180,u=12345'); + }); + + it('should send request with different media type', function() { + const types = { + 'audio': 'a', + 'banner': 'b', + 'native': 'n', + 'video': 'v' + } + Object.keys(types).forEach(key => { + const adunitIdValidBidRequests = [ + { + ...validBidRequests[0], + ...{ + mediaTypes: { + [key]: { + sizes: [300, 250] + } + } + } + } + ]; + + const request = spec.buildRequests(adunitIdValidBidRequests, { })[0]; + assert.equal(request.data.bids, `bidId:t=${types[key]},s=980x120_980x180,c=/id/site1/header-ad`); + }) + }); + + describe('gdpr', function() { + let mockConfig; + + beforeEach(function () { + mockConfig = { + consentManagement: { + cmpApi: 'iab' + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send GDPR Consent data', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + assert.equal(request.iab_consent, bidderRequest.gdprConsent.consentString); + }); + }); + + describe('dsu', function() { + it('should pass DSU from local storage if set', function() { + let dsu = 'some_dsu'; + localStorage.setItem('apr_dsu', dsu); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + + assert.equal(request.dsu, dsu); + }); + + it('should generate new DSU if nothing in local storage', function() { + localStorage.removeItem('apr_dsu'); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '394': true + } + } + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + let dsu = localStorage.getItem('apr_dsu'); + + assert.isNotEmpty(dsu); + assert.equal(request.dsu, dsu); + }); + }); + }); + + describe('dsu config', function() { + let mockConfig; + beforeEach(function () { + mockConfig = { + apstream: { + noDsu: true + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should not send DSU if it is disabled in config', function() { + const request = spec.buildRequests(validBidRequests, { })[0]; + + assert.equal(request.data.dsu, ''); + }); + }); + + describe('interpretResponse', function () { + it('should return empty array if no body in response', function () { + const serverResponse = {}; + const bidRequest = {}; + + assert.isEmpty(spec.interpretResponse(serverResponse, bidRequest)); + }); + + it('should map server response', function () { + const serverResponse = { + body: [ + { + bidId: 123, + bidDetails: { + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + netRevenue: 'true', + creativeId: '1234', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360 + } + } + ] + }; + const bidRequest = {}; + + const response = spec.interpretResponse(serverResponse, bidRequest); + + const expected = { + requestId: 123, + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + creativeId: '1234', + netRevenue: 'true', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360 + }; + + assert.deepEqual(response[0], expected); + }); + + it('should add pixels to ad', function () { + const serverResponse = { + body: [ + { + bidId: 123, + bidDetails: { + cpm: 1.23, + width: 980, + height: 300, + currency: 'DKK', + creativeId: '1234', + netRevenue: 'true', + dealId: '99008', + ad: '

Buy our something!

', + ttl: 360, + noticeUrls: [ + 'site1', + 'site2' + ], + impressionScripts: [ + 'url_to_script' + ] + } + } + ] + }; + const bidRequest = {}; + + const response = spec.interpretResponse(serverResponse, bidRequest); + + assert.match(response[0].ad, /site1/); + assert.match(response[0].ad, /site2/); + }); + }); +}); diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js deleted file mode 100644 index 04694731981..00000000000 --- a/test/spec/modules/audienceNetworkBidAdapter_spec.js +++ /dev/null @@ -1,568 +0,0 @@ -/** - * @file Tests for AudienceNetwork adapter. - */ -import { expect } from 'chai'; - -import { spec } from 'modules/audienceNetworkBidAdapter.js'; -import * as utils from 'src/utils.js'; - -const { - code, - supportedMediaTypes, - isBidRequestValid, - buildRequests, - interpretResponse -} = spec; - -const bidder = 'audienceNetwork'; -const placementId = 'test-placement-id'; -const playerwidth = 320; -const playerheight = 180; -const requestId = 'test-request-id'; -const debug = 'adapterver=1.3.0&platform=241394079772386&platver=$prebid.version$&cb=test-uuid'; -const expectedPageUrl = encodeURIComponent('http://www.prebid-test.com/audienceNetworkTest.html?pbjs_debug=true'); -const mockBidderRequest = { - bidderCode: 'audienceNetwork', - auctionId: '720146a0-8f32-4db0-bb30-21f1d057efb4', - bidderRequestId: '1312d48561657e', - auctionStart: 1579711852920, - timeout: 3000, - refererInfo: { - referer: 'http://www.prebid-test.com/audienceNetworkTest.html?pbjs_debug=true', - reachedTop: true, - numIframes: 0, - stack: ['http://www.prebid-test.com/audienceNetworkTest.html?pbjs_debug=true'], - canonicalUrl: undefined - }, - start: 1579711852925 -}; - -describe('AudienceNetwork adapter', function () { - describe('Public API', function () { - it('code', function () { - expect(code).to.equal(bidder); - }); - it('supportedMediaTypes', function () { - expect(supportedMediaTypes).to.deep.equal(['banner', 'video']); - }); - it('isBidRequestValid', function () { - expect(isBidRequestValid).to.be.a('function'); - }); - it('buildRequests', function () { - expect(buildRequests).to.be.a('function'); - }); - it('interpretResponse', function () { - expect(interpretResponse).to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - it('missing placementId parameter', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250]] - })).to.equal(false); - }); - - it('invalid sizes parameter', function () { - expect(isBidRequestValid({ - bidder, - sizes: ['', undefined, null, '300x100', [300, 100], [300], {}], - params: { placementId } - })).to.equal(false); - }); - - it('valid when at least one valid size', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[1, 1], [300, 250]], - params: { placementId } - })).to.equal(true); - }); - - it('valid parameters', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250], [320, 50]], - params: { placementId } - })).to.equal(true); - }); - - it('fullwidth', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250], [336, 280]], - params: { - placementId, - format: 'fullwidth' - } - })).to.equal(true); - }); - - it('native', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[300, 250]], - params: { - placementId, - format: 'native' - } - })).to.equal(true); - }); - - it('native with non-IAB size', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[728, 90]], - params: { - placementId, - format: 'native' - } - })).to.equal(true); - }); - - it('video', function () { - expect(isBidRequestValid({ - bidder, - sizes: [[playerwidth, playerheight]], - params: { - placementId, - format: 'video' - } - })).to.equal(true); - }); - }); - - describe('buildRequests', function () { - before(function () { - sinon - .stub(utils, 'generateUUID') - .returns('test-uuid'); - }); - - after(function () { - utils.generateUUID.restore(); - }); - - it('takes the canonicalUrl as priority over referer for pageurl', function () { - let expectedCanonicalUrl = encodeURIComponent('http://www.this-is-canonical-url.com/hello-world.html?pbjs_debug=true'); - mockBidderRequest.refererInfo.canonicalUrl = 'http://www.this-is-canonical-url.com/hello-world.html?pbjs_debug=true'; - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[300, 50], [300, 250], [320, 50]], - params: { placementId } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['300x250'], - method: 'GET', - pageurl: expectedCanonicalUrl, - requestIds: [requestId], - sizes: ['300x250'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${expectedCanonicalUrl}&sdk[]=6.0.web&${debug}` - }]); - mockBidderRequest.refererInfo.canonicalUrl = undefined; - }); - - it('can build URL for IAB unit', function () { - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[300, 50], [300, 250], [320, 50]], - params: { placementId } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['300x250'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['300x250'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${expectedPageUrl}&sdk[]=6.0.web&${debug}` - }]); - }); - - it('can build URL for video unit', function () { - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[640, 480]], - params: { - placementId, - format: 'video' - } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['video'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['640x480'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=video&testmode=false&pageurl=${expectedPageUrl}&sdk[]=&${debug}&playerwidth=640&playerheight=480` - }]); - }); - - it('can build URL for native unit in non-IAB size', function () { - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[728, 90]], - params: { - placementId, - format: 'native' - } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['native'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['728x90'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=native&testmode=false&pageurl=${expectedPageUrl}&sdk[]=6.0.web&${debug}` - }]); - }); - - it('can build URL for deprecated fullwidth unit, overriding platform', function () { - const platform = 'test-platform'; - const debugPlatform = debug.replace('241394079772386', platform); - - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[300, 250]], - params: { - placementId, - platform, - format: 'fullwidth' - } - }], mockBidderRequest)).to.deep.equal([{ - adformats: ['300x250'], - method: 'GET', - pageurl: expectedPageUrl, - requestIds: [requestId], - sizes: ['300x250'], - url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${expectedPageUrl}&sdk[]=6.0.web&${debugPlatform}` - }]); - }); - }); - - describe('interpretResponse', function () { - it('error in response', function () { - expect(interpretResponse({ - body: { - errors: ['test-error-message'] - } - }, {})).to.deep.equal([]); - }); - - it('valid native bid in response', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['native'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ad) - .to.contain(`placementid: '${placementId}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: 'test-bid-id',`) - .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') - .and.to.contain('
', 'ad missing native container'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('native'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - - it('valid IAB bid in response', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['300x250'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ad) - .to.contain(`placementid: '${placementId}',`) - .and.to.contain(`format: '300x250',`) - .and.to.contain(`bidid: 'test-bid-id',`) - .and.not.to.contain('getElementsByTagName("style")', 'ad should not contain native styles') - .and.not.to.contain('
', 'ad should not contain native container'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('300x250'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - - it('filters invalid slot sizes', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['300x250'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('300x250'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - - it('valid multiple bids in response', function () { - const placementIdNative = 'test-placement-id-native'; - const placementIdIab = 'test-placement-id-iab'; - - const [bidResponseNative, bidResponseIab] = interpretResponse({ - body: { - errors: [], - bids: { - [placementIdNative]: [{ - placement_id: placementIdNative, - bid_id: 'test-bid-id-native', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }], - [placementIdIab]: [{ - placement_id: placementIdIab, - bid_id: 'test-bid-id-iab', - bid_price_cents: 456, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['native', '300x250'], - requestIds: [requestId, requestId], - sizes: ['300x250', [300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponseNative.cpm).to.equal(1.23); - expect(bidResponseNative.requestId).to.equal(requestId); - expect(bidResponseNative.width).to.equal(300); - expect(bidResponseNative.height).to.equal(250); - expect(bidResponseNative.ad) - .to.contain(`placementid: '${placementIdNative}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: 'test-bid-id-native',`); - expect(bidResponseNative.ttl).to.equal(600); - expect(bidResponseNative.creativeId).to.equal(placementIdNative); - expect(bidResponseNative.netRevenue).to.equal(true); - expect(bidResponseNative.currency).to.equal('USD'); - expect(bidResponseNative.hb_bidder).to.equal('fan'); - expect(bidResponseNative.fb_bidid).to.equal('test-bid-id-native'); - expect(bidResponseNative.fb_format).to.equal('native'); - expect(bidResponseNative.fb_placementid).to.equal(placementIdNative); - - expect(bidResponseIab.cpm).to.equal(4.56); - expect(bidResponseIab.requestId).to.equal(requestId); - expect(bidResponseIab.width).to.equal(300); - expect(bidResponseIab.height).to.equal(250); - expect(bidResponseIab.ad) - .to.contain(`placementid: '${placementIdIab}',`) - .and.to.contain(`format: '300x250',`) - .and.to.contain(`bidid: 'test-bid-id-iab',`); - expect(bidResponseIab.ttl).to.equal(600); - expect(bidResponseIab.creativeId).to.equal(placementIdIab); - expect(bidResponseIab.netRevenue).to.equal(true); - expect(bidResponseIab.currency).to.equal('USD'); - expect(bidResponseIab.hb_bidder).to.equal('fan'); - expect(bidResponseIab.fb_bidid).to.equal('test-bid-id-iab'); - expect(bidResponseIab.fb_format).to.equal('300x250'); - expect(bidResponseIab.fb_placementid).to.equal(placementIdIab); - }); - - it('valid video bid in response', function () { - const bidId = 'test-bid-id-video'; - - const [bidResponse] = interpretResponse({ - body: { - errors: [], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: bidId, - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['video'], - requestIds: [requestId], - sizes: [[playerwidth, playerheight]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.ttl).to.equal(3600); - expect(bidResponse.mediaType).to.equal('video'); - expect(bidResponse.vastUrl).to.equal(`https://an.facebook.com/v1/instream/vast.xml?placementid=${placementId}&pageurl=${expectedPageUrl}&playerwidth=${playerwidth}&playerheight=${playerheight}&bidid=${bidId}`); - expect(bidResponse.width).to.equal(playerwidth); - expect(bidResponse.height).to.equal(playerheight); - }); - - it('mixed video and native bids', function () { - const videoPlacementId = 'test-video-placement-id'; - const videoBidId = 'test-video-bid-id'; - const nativePlacementId = 'test-native-placement-id'; - const nativeBidId = 'test-native-bid-id'; - - const [bidResponseVideo, bidResponseNative] = interpretResponse({ - body: { - errors: [], - bids: { - [videoPlacementId]: [{ - placement_id: videoPlacementId, - bid_id: videoBidId, - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }], - [nativePlacementId]: [{ - placement_id: nativePlacementId, - bid_id: nativeBidId, - bid_price_cents: 456, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['video', 'native'], - requestIds: [requestId, requestId], - sizes: [[playerwidth, playerheight], [300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponseVideo.cpm).to.equal(1.23); - expect(bidResponseVideo.requestId).to.equal(requestId); - expect(bidResponseVideo.ttl).to.equal(3600); - expect(bidResponseVideo.mediaType).to.equal('video'); - expect(bidResponseVideo.vastUrl).to.equal(`https://an.facebook.com/v1/instream/vast.xml?placementid=${videoPlacementId}&pageurl=${expectedPageUrl}&playerwidth=${playerwidth}&playerheight=${playerheight}&bidid=${videoBidId}`); - expect(bidResponseVideo.width).to.equal(playerwidth); - expect(bidResponseVideo.height).to.equal(playerheight); - - expect(bidResponseNative.cpm).to.equal(4.56); - expect(bidResponseNative.requestId).to.equal(requestId); - expect(bidResponseNative.ttl).to.equal(600); - expect(bidResponseNative.width).to.equal(300); - expect(bidResponseNative.height).to.equal(250); - expect(bidResponseNative.ad) - .to.contain(`placementid: '${nativePlacementId}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: '${nativeBidId}',`); - }); - - it('mixture of valid native bid and error in response', function () { - const [bidResponse] = interpretResponse({ - body: { - errors: ['test-error-message'], - bids: { - [placementId]: [{ - placement_id: placementId, - bid_id: 'test-bid-id', - bid_price_cents: 123, - bid_price_currency: 'usd', - bid_price_model: 'cpm' - }] - } - } - }, { - adformats: ['native'], - requestIds: [requestId], - sizes: [[300, 250]], - pageurl: expectedPageUrl - }); - - expect(bidResponse.cpm).to.equal(1.23); - expect(bidResponse.requestId).to.equal(requestId); - expect(bidResponse.width).to.equal(300); - expect(bidResponse.height).to.equal(250); - expect(bidResponse.ad) - .to.contain(`placementid: '${placementId}',`) - .and.to.contain(`format: 'native',`) - .and.to.contain(`bidid: 'test-bid-id',`) - .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') - .and.to.contain('
', 'ad missing native container'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.creativeId).to.equal(placementId); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.currency).to.equal('USD'); - - expect(bidResponse.hb_bidder).to.equal('fan'); - expect(bidResponse.fb_bidid).to.equal('test-bid-id'); - expect(bidResponse.fb_format).to.equal('native'); - expect(bidResponse.fb_placementid).to.equal(placementId); - }); - }); -}); diff --git a/test/spec/modules/automatadBidAdapter_spec.js b/test/spec/modules/automatadBidAdapter_spec.js index 788fd3aefc4..a6de8810284 100644 --- a/test/spec/modules/automatadBidAdapter_spec.js +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -95,6 +95,11 @@ describe('automatadBidAdapter', function () { let r = rdata.imp[0] expect(r.siteID !== null && r.placementID !== null).to.be.true }) + + it('should include adunit code', function () { + let r = rdata.imp[0] + expect(r.adUnitCode !== null).to.be.true + }) }) describe('interpretResponse', function () { diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js new file mode 100644 index 00000000000..a353665ec33 --- /dev/null +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -0,0 +1,281 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/boldwinBidAdapter.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; + +describe('BoldwinBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'boldwin', + params: { + placementId: 0, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://ssp.videowalldirect.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'hPlayer', 'wPlayer', 'schain'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://cs.videowalldirect.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js new file mode 100644 index 00000000000..3b7e46e55a7 --- /dev/null +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/brightMountainMediaBidAdapter.js'; + +describe('brightMountainMediaBidAdapter_spec', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'brightmountainmedia', + bidderRequestId: '145e1d6a7837c9', + params: { + placement_id: '123qwerty' + }, + placementCode: 'placementid_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + }; + let bidderRequest = { + bidderCode: 'brightmountainmedia', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + uspConsent: '1YN-', + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + bids: [bid] + } + + describe('isBidRequestValid', function () { + it('Should return true when when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when required params are not passed', function () { + bid.params = {} + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://console.brightmountainmedia.com/hb/bid'); + }); + + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes'); + expect(placement.placementId).to.be.a('string'); + expect(placement.bidId).to.be.a('string'); + expect(placement.traffic).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + } + }); + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + let resObject = { + body: [{ + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + }] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + + describe('getUserSyncs', function () { + let syncoptionsIframe = { + 'iframeEnabled': 'true' + } + it('should return iframe sync option', function () { + expect(spec.getUserSyncs(syncoptionsIframe)).to.be.an('array').with.lengthOf(1); + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.exist; + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.exist; + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe') + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('https://console.brightmountainmedia.com:4444/cookieSync') + }); + }); +}); diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js index 6477e94d9d6..a89391d681e 100644 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -141,6 +141,31 @@ describe('brightcomBidAdapter', function() { expect(payload.site.publisher.id).to.equal(1234567); }); + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'brightcom', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + context('when element is fully in view', function() { it('returns 100', function() { Object.assign(element, { width: 600, height: 400 }); diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index 626018241c4..aef4fb562a7 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -14,7 +14,8 @@ describe('ConnectAd Adapter', function () { bidder: 'conntectad', params: { siteId: 123456, - networkId: 123456 + networkId: 123456, + bidfloor: 0.50 }, adUnitCode: '/19968336/header-bid-tag-1', mediaTypes: { @@ -46,8 +47,7 @@ describe('ConnectAd Adapter', function () { bidderRequestId: '1c56ad30b9b8ca8', transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', userId: { - tdid: '123456', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}} + tdid: '123456' } }]; @@ -154,6 +154,23 @@ describe('ConnectAd Adapter', function () { expect(requestparse.placements[0].networkId).to.equal(123456); }); + it('should process floors module if available', function() { + const floorInfo = { + currency: 'USD', + floor: 5.20 + }; + bidRequests[0].getFloor = () => floorInfo; + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].floor).to.equal(5.20); + }); + + it('should be 0 if no floormodule is available', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].floor).to.equal(0); + }); + it('should contain gdpr info', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); @@ -227,7 +244,6 @@ describe('ConnectAd Adapter', function () { const requestparse = JSON.parse(request.data); expect(requestparse.user.ext.eids).to.be.an('array'); expect(requestparse.user.ext.eids[0].uids[0].id).to.equal('123456'); - expect(requestparse.user.ext.digitrust.id).to.equal('DTID'); }); it('should add referer info', function () { diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index c4f6fe70dd1..deaacbc5a28 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -24,9 +24,8 @@ describe('consentManagement', function () { setConsentConfig({}); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(10000); - expect(allowAuction).to.be.true; expect(gdprScope).to.be.equal(false); - sinon.assert.callCount(utils.logInfo, 4); + sinon.assert.callCount(utils.logInfo, 3); }); it('should exit consent manager if config is not an object', function () { @@ -58,7 +57,10 @@ describe('consentManagement', function () { setConsentConfig(allConfig); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(7500); - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(gdprScope).to.be.true; }); @@ -110,7 +112,10 @@ describe('consentManagement', function () { expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(3333); - expect(allowAuction).to.be.equal(false); + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(gdprScope).to.be.equal(false); }); }); @@ -164,7 +169,10 @@ describe('consentManagement', function () { setConsentConfig(staticConfig); expect(userCMP).to.be.equal('static'); expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(staticConsentData).to.be.equal(staticConfig.consentData); }); @@ -244,7 +252,10 @@ describe('consentManagement', function () { setConsentConfig(staticConfig); expect(userCMP).to.be.equal('static'); expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(allowAuction).to.be.false; + expect(allowAuction).to.deep.equal({ + value: false, + definedInConfig: true + }); expect(gdprScope).to.be.equal(false); expect(staticConsentData).to.be.equal(staticConfig.consentData); }); @@ -423,7 +434,6 @@ describe('consentManagement', function () { setConsentConfig(goodConfigWithAllowAuction); requestBidsHook(() => { let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); expect(consent.consentString).to.equal(tarConsentString); expect(consent.gdprApplies).to.be.true; @@ -626,7 +636,6 @@ describe('consentManagement', function () { didHookReturn = true; }, {}); let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); @@ -634,7 +643,33 @@ describe('consentManagement', function () { expect(consent.apiVersion).to.equal(2); }); - it('throws an error when processCmpData check failed while config had allowAuction set to false', function () { + it('performs lookup check and stores consentData for a valid existing user with additional consent', function () { + let testConsentData = { + tcString: 'abc12345234', + addtlConsent: 'superduperstring', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded' + }; + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfigWithAllowAuction); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gdprDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.consentString).to.equal(testConsentData.tcString); + expect(consent.addtlConsent).to.equal(testConsentData.addtlConsent); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(2); + }); + + it('throws an error when processCmpData check fails + does not call requestBids callbcack even when allowAuction is true', function () { let testConsentData = {}; let bidsBackHandlerReturn = false; @@ -642,7 +677,7 @@ describe('consentManagement', function () { args[2](testConsentData); }); - setConsentConfig(goodConfigWithCancelAuction); + setConsentConfig(goodConfigWithAllowAuction); requestBidsHook(() => { didHookReturn = true; @@ -650,6 +685,7 @@ describe('consentManagement', function () { let consent = gdprDataHandler.getConsentData(); sinon.assert.calledOnce(utils.logError); + sinon.assert.notCalled(utils.logWarn); expect(didHookReturn).to.be.false; expect(bidsBackHandlerReturn).to.be.true; expect(consent).to.be.null; @@ -676,34 +712,12 @@ describe('consentManagement', function () { didHookReturn = true; }, {}); let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); expect(consent.gdprApplies).to.be.true; expect(consent.apiVersion).to.equal(2); }); - - it('throws a warning + stores consentData + calls callback when processCmpData check failed while config had allowAuction set to true', function () { - let testConsentData = {}; - - cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { - args[2](testConsentData); - }); - - setConsentConfig(goodConfigWithAllowAuction); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); - let consent = gdprDataHandler.getConsentData(); - - sinon.assert.calledOnce(utils.logWarn); - expect(didHookReturn).to.be.true; - expect(consent.consentString).to.be.undefined; - expect(consent.gdprApplies).to.be.false; - expect(consent.apiVersion).to.equal(2); - }); }); }); }); diff --git a/test/spec/modules/digitrustIdSystem_spec.js b/test/spec/modules/digitrustIdSystem_spec.js deleted file mode 100644 index befd6eb75b6..00000000000 --- a/test/spec/modules/digitrustIdSystem_spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import { - digiTrustIdSubmodule, - surfaceTestHook -} from 'modules/digiTrustIdSystem.js'; - -let assert = require('chai').assert; -let expect = require('chai').expect; -var testHook = null; - -/** -* A mock implementation of IAB Consent Provider -*/ -function mockCmp(command, version, callback, parameter) { - var resultVal; - if (command == 'ping') { - resultVal = { - gdprAppliesGlobally: mockCmp.stubSettings.isGlobal - }; - callback(resultVal); - } else if (command == 'getVendorConsents') { - let cbResult = { - vendorConsents: [] - } - cbResult.vendorConsents[version] = mockCmp.stubSettings.consents; - callback(cbResult); - } -} - -mockCmp.stubSettings = { - isGlobal: false, - consents: true -}; - -function setupCmpMock(isGlobal, consents) { - window.__cmp = mockCmp; - mockCmp.stubSettings.isGlobal = isGlobal; - mockCmp.stubSettings.consents = consents; -} - -describe('DigiTrust Id System', function () { - it('Should create the test hook', function (done) { - testHook = surfaceTestHook(); - assert.isNotNull(testHook, 'The test hook failed to surface'); - var conf = { - init: { - member: 'unit_test', - site: 'foo' - }, - callback: function (result) { - } - }; - testHook.initDigitrustFacade(conf); - window.DigiTrust.getUser(conf); - expect(window.DigiTrust).to.exist; - expect(window.DigiTrust.isMock).to.be.true; - done(); - }); - - it('Should report as client', function (done) { - delete window.DigiTrust; - testHook = surfaceTestHook(); - - var conf = { - init: { - member: 'unit_test', - site: 'foo' - }, - callback: function (result) { - expect(window.DigiTrust).to.exist; - expect(result).to.exist; - expect(window.DigiTrust.isMock).to.be.true; - } - }; - testHook.initDigitrustFacade(conf); - expect(window.DigiTrust).to.exist; - expect(window.DigiTrust.isClient).to.be.true; - done(); - }); - - it('Should allow consent when given', function (done) { - testHook = surfaceTestHook(); - setupCmpMock(true, true); - var handler = function(result) { - expect(result).to.be.true; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); - - it('Should consent if does not apply', function (done) { - testHook = surfaceTestHook(); - setupCmpMock(false, true); - var handler = function (result) { - expect(result).to.be.true; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); - - it('Should not allow consent when not given', function (done) { - testHook = surfaceTestHook(); - setupCmpMock(true, false); - var handler = function (result) { - expect(result).to.be.false; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); - it('Should deny consent if timeout', function (done) { - window.__cmp = function () { }; - var handler = function (result) { - expect(result).to.be.false; - done(); - } - - testHook.gdpr.hasConsent({ consentTimeout: 1 }, handler); - }); - it('Should pass consent test if cmp not present', function (done) { - delete window.__cmp - testHook = surfaceTestHook(); - var handler = function (result) { - expect(result).to.be.true; - done(); - } - - testHook.gdpr.hasConsent(null, handler); - }); -}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 1cbc2911ef5..434e1841a0f 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -43,7 +43,9 @@ describe('eids array generation for known sub-modules', function() { it('parrableId', function() { const userId = { - parrableid: 'some-random-id-value' + parrableId: { + eid: 'some-random-id-value' + } }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); @@ -119,22 +121,6 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('DigiTrust; getValue call', function() { - const userId = { - digitrustid: { - data: { - id: 'some-random-id-value' - } - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'digitru.st', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - it('criteo', function() { const userId = { criteoId: 'some-random-id-value' diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 7be8a2ce5ac..138786b9c74 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -612,12 +612,23 @@ describe('emx_digital Adapter', function () { }); describe('getUserSyncs', function () { - let syncOptionsIframe = { iframeEnabled: true }; - let syncOptionsPixel = { pixelEnabled: true }; - it('Should push the correct sync type depending on the config', function () { - let iframeSync = spec.getUserSyncs(syncOptionsIframe); - expect(iframeSync.length).to.equal(1); - expect(iframeSync[0].type).to.equal('iframe'); + it('should register the iframe sync url', function () { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); + + it('should pass gdpr params', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=0'); }); }); }); diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 7f4828267a9..d5c8d7bfb88 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,11 +1,22 @@ -import { deviceAccessHook, setEnforcementConfig, userSyncHook, userIdHook } from 'modules/gdprEnforcement.js'; +import { + deviceAccessHook, + setEnforcementConfig, + userSyncHook, + userIdHook, + makeBidRequestsHook, + validateRules, + enforcementRules, + purpose1Rule, + purpose2Rule, + enableAnalyticsHook +} from 'modules/gdprEnforcement.js'; import { config } from 'src/config.js'; import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { validateStorageEnforcement } from 'src/storageManager.js'; -import { executeStorageCallbacks } from 'src/prebid.js'; +import events from 'src/events.js'; -describe('gdpr enforcement', function() { +describe('gdpr enforcement', function () { let nextFnSpy; let logWarnSpy; let gdprDataHandlerStub; @@ -34,11 +45,12 @@ describe('gdpr enforcement', function() { 'consents': { '1': true, '2': true, - '3': true + '3': true, + '7': true }, 'legitimateInterests': { '1': false, - '2': false, + '2': true, '3': false } }, @@ -46,7 +58,9 @@ describe('gdpr enforcement', function() { 'consents': { '1': true, '2': true, - '3': false + '3': false, + '4': true, + '5': false }, 'legitimateInterests': { '1': false, @@ -81,23 +95,38 @@ describe('gdpr enforcement', function() { } }; - after(function() { - validateStorageEnforcement.getHooks({hook: deviceAccessHook}).remove(); - $$PREBID_GLOBAL$$.requestBids.getHooks({hook: executeStorageCallbacks}).remove(); + after(function () { + validateStorageEnforcement.getHooks({ hook: deviceAccessHook }).remove(); + $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); + adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) - describe('deviceAccessHook', function() { - beforeEach(function() { + describe('deviceAccessHook', function () { + let adapterManagerStub; + + function getBidderSpec(gvlid) { + return { + getSpec: () => { + return { + gvlid + } + } + } + } + + beforeEach(function () { nextFnSpy = sinon.spy(); gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); logWarnSpy = sinon.spy(utils, 'logWarn'); + adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); }); - afterEach(function() { + afterEach(function () { config.resetConfig(); gdprDataHandler.getConsentData.restore(); logWarnSpy.restore(); + adapterManagerStub.restore(); }); - it('should not allow device access when device access flag is set to false', function() { + it('should not allow device access when device access flag is set to false', function () { config.setConfig({ deviceAccess: false, consentManagement: { @@ -118,10 +147,12 @@ describe('gdpr enforcement', function() { hasEnforcementHook: true, valid: false } - expect(nextFnSpy.calledWith(undefined, result)); + sinon.assert.calledWith(nextFnSpy, undefined, undefined, result); }); - it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function() { + it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(5)); setEnforcementConfig({ gdpr: { rules: [{ @@ -143,7 +174,9 @@ describe('gdpr enforcement', function() { expect(logWarnSpy.callCount).to.equal(0); }); - it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function() { + it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(3)); setEnforcementConfig({ gdpr: { rules: [{ @@ -164,7 +197,8 @@ describe('gdpr enforcement', function() { expect(logWarnSpy.callCount).to.equal(1); }); - it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function() { + it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () { + adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); setEnforcementConfig({ gdpr: { rules: [{ @@ -187,15 +221,83 @@ describe('gdpr enforcement', function() { hasEnforcementHook: true, valid: true } - expect(nextFnSpy.calledWith(undefined, result)); + sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); + }); + + it('should use gvlMapping set by publisher', function() { + config.setConfig({ + 'gvlMapping': { + 'appnexus': 4 + } + }); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: true + } + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + config.resetConfig(); + }); + + it('should use gvl id of alias and not of parent', function() { + let curBidderStub = sinon.stub(config, 'getCurrentBidder'); + curBidderStub.returns('appnexus-alias'); + adapterManager.aliasBidAdapter('appnexus', 'appnexus-alias'); + config.setConfig({ + 'gvlMapping': { + 'appnexus-alias': 4 + } + }); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] + } + }); + let consentData = {} + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.gdprApplies = true; + consentData.apiVersion = 2; + gdprDataHandlerStub.returns(consentData); + + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + expect(nextFnSpy.calledOnce).to.equal(true); + let result = { + hasEnforcementHook: true, + valid: true + } + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + config.resetConfig(); + curBidderStub.restore(); }); }); - describe('userSyncHook', function() { + describe('userSyncHook', function () { let curBidderStub; let adapterManagerStub; - beforeEach(function() { + beforeEach(function () { gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); logWarnSpy = sinon.spy(utils, 'logWarn'); curBidderStub = sinon.stub(config, 'getCurrentBidder'); @@ -203,7 +305,7 @@ describe('gdpr enforcement', function() { nextFnSpy = sinon.spy(); }); - afterEach(function() { + afterEach(function () { config.getCurrentBidder.restore(); config.resetConfig(); gdprDataHandler.getConsentData.restore(); @@ -211,7 +313,7 @@ describe('gdpr enforcement', function() { logWarnSpy.restore(); }); - it('should allow bidder to do user sync if consent is true', function() { + it('should allow bidder to do user sync if consent is true', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -230,7 +332,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder1'); adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 1 } @@ -240,7 +342,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder2'); adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 3 } @@ -250,7 +352,7 @@ describe('gdpr enforcement', function() { expect(nextFnSpy.calledTwice).to.equal(true); }); - it('should not allow bidder to do user sync if user has denied consent', function() { + it('should not allow bidder to do user sync if user has denied consent', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -269,7 +371,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder1'); adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 1 } @@ -279,7 +381,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder2'); adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 3 } @@ -290,7 +392,7 @@ describe('gdpr enforcement', function() { expect(logWarnSpy.callCount).to.equal(1); }); - it('should not check vendor consent when enforceVendor is false', function() { + it('should not check vendor consent when enforceVendor is false', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -309,7 +411,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder1'); adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 1 } @@ -319,7 +421,7 @@ describe('gdpr enforcement', function() { curBidderStub.returns('sampleBidder2'); adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function() { + getSpec: function () { return { 'gvlid': 3 } @@ -331,16 +433,16 @@ describe('gdpr enforcement', function() { }); }); - describe('userIdHook', function() { - beforeEach(function() { + describe('userIdHook', function () { + beforeEach(function () { logWarnSpy = sinon.spy(utils, 'logWarn'); nextFnSpy = sinon.spy(); }); - afterEach(function() { + afterEach(function () { config.resetConfig(); logWarnSpy.restore(); }); - it('should allow user id module if consent is given', function() { + it('should allow user id module if consent is given', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -366,9 +468,10 @@ describe('gdpr enforcement', function() { const args = nextFnSpy.getCalls()[0].args; expect(args[1].hasValidated).to.be.true; expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, submodules, { ...consentData, hasValidated: true }); }); - it('should allow userId module if gdpr not in scope', function() { + it('should allow userId module if gdpr not in scope', function () { let submodules = [{ submodule: { gvlid: 1, @@ -381,10 +484,10 @@ describe('gdpr enforcement', function() { const args = nextFnSpy.getCalls()[0].args; expect(args[1]).to.be.null; expect(nextFnSpy.calledOnce).to.equal(true); - expect(nextFnSpy.calledWith(undefined, submodules, consentData)); + sinon.assert.calledWith(nextFnSpy, submodules, consentData); }); - it('should not enforce if not apiVersion 2', function() { + it('should not allow user id module if user denied consent', function () { setEnforcementConfig({ gdpr: { rules: [{ @@ -397,57 +500,571 @@ describe('gdpr enforcement', function() { }); let consentData = {} consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 1; + consentData.apiVersion = 2; consentData.gdprApplies = true; + let submodules = [{ submodule: { gvlid: 1, name: 'sampleUserId' } + }, { + submodule: { + gvlid: 3, + name: 'sampleUserId1' + } }] userIdHook(nextFnSpy, submodules, consentData); - // Should not pass back hasValidated flag since version 1 - const args = nextFnSpy.getCalls()[0].args; - expect(args[1].hasValidated).to.be.undefined; - expect(args[0]).to.deep.equal(submodules); - expect(nextFnSpy.calledOnce).to.equal(true); + expect(logWarnSpy.callCount).to.equal(1); + let expectedSubmodules = [{ + submodule: { + gvlid: 1, + name: 'sampleUserId' + } + }] + sinon.assert.calledWith(nextFnSpy, expectedSubmodules, { ...consentData, hasValidated: true }); + }); + }); + + describe('makeBidRequestsHook', function () { + let sandbox; + let adapterManagerStub; + let emitEventSpy; + + const MOCK_AD_UNITS = [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [{ + bidder: 'bidder_1' // has consent + }, { + bidder: 'bidder_2' // doesn't have consent, but liTransparency is true. Bidder remains active. + }] + }, { + code: 'ad-unit-2', + mediaTypes: {}, + bids: [{ + bidder: 'bidder_2' + }, { + bidder: 'bidder_3' + }] + }]; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); + adapterManagerStub = sandbox.stub(adapterManager, 'getBidAdapter'); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + nextFnSpy = sandbox.spy(); + emitEventSpy = sandbox.spy(events, 'emit'); + }); + afterEach(function () { + config.resetConfig(); + sandbox.restore(); }); - it('should not allow user id module if user denied consent', function() { + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is established)', function () { setEnforcementConfig({ gdpr: { rules: [{ - purpose: 'storage', - enforcePurpose: false, + purpose: 'basicAds', + enforcePurpose: true, enforceVendor: true, vendorExceptions: [] }] } }); - let consentData = {} + const consentData = {}; consentData.vendorData = staticConfig.consentData.getTCData; consentData.apiVersion = 2; consentData.gdprApplies = true; - let submodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('bidder_1').returns({ + getSpec: function () { + return { 'gvlid': 4 } + } + }); + adapterManagerStub.withArgs('bidder_2').returns({ + getSpec: function () { + return { 'gvlid': 5 } + } + }); + adapterManagerStub.withArgs('bidder_3').returns({ + getSpec: function () { + return { 'gvlid': undefined } } + }); + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_1' }), + sinon.match({ bidder: 'bidder_2' }) + ] }, { - submodule: { - gvlid: 3, - name: 'sampleUserId1' + code: 'ad-unit-2', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_2' }), + sinon.match({ bidder: 'bidder_3' }) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. + ] + }], []); + }); + + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ['bidder_3'] + }] } - }] - userIdHook(nextFnSpy, submodules, consentData); - expect(logWarnSpy.callCount).to.equal(1); - let expectedSubmodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' + }); + const consentData = {}; + + // set li for purpose 2 to false + const newConsentData = utils.deepClone(staticConfig); + newConsentData.consentData.getTCData.purpose.legitimateInterests['2'] = false; + + consentData.vendorData = newConsentData.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('bidder_1').returns({ + getSpec: function () { + return { 'gvlid': 4 } } + }); + adapterManagerStub.withArgs('bidder_2').returns({ + getSpec: function () { + return { 'gvlid': 5 } + } + }); + adapterManagerStub.withArgs('bidder_3').returns({ + getSpec: function () { + return { 'gvlid': undefined } + } + }); + + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + code: 'ad-unit-1', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_1' }), // 'bidder_2' is not present because it doesn't have vendorConsent + ] + }, { + code: 'ad-unit-2', + mediaTypes: {}, + bids: [ + sinon.match({ bidder: 'bidder_3' }), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions + ] + }], []); + + expect(logWarnSpy.calledOnce).to.equal(true); + }); + + it('should skip validation checks if GDPR version is not equal to "2"', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforePurpose: false, + enforceVendor: false, + vendorExceptions: [] + }] + } + }); + + const consentData = {}; + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 1; + consentData.gdprApplies = true; + gdprDataHandlerStub.returns(consentData); + + makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, sinon.match.array.deepEquals(MOCK_AD_UNITS), []); + expect(emitEventSpy.notCalled).to.equal(true); + expect(logWarnSpy.notCalled).to.equal(true); + }); + }); + + describe('enableAnalyticsHook', function () { + let sandbox; + let adapterManagerStub; + + const MOCK_ANALYTICS_ADAPTER_CONFIG = [{ + provider: 'analyticsAdapter_A', + options: {} + }, { + provider: 'analyticsAdapter_B', + options: {} + }, { + provider: 'analyticsAdapter_C', + options: {} + }]; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); + adapterManagerStub = sandbox.stub(adapterManager, 'getAnalyticsAdapter'); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + nextFnSpy = sandbox.spy(); + }); + + afterEach(function() { + config.resetConfig(); + sandbox.restore(); + }); + + it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function() { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'measurement', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ['analyticsAdapter_B'] + }] + } + }); + + const consentData = {}; + consentData.vendorData = staticConfig.consentData.getTCData; + consentData.apiVersion = 2; + consentData.gdprApplies = true; + + gdprDataHandlerStub.returns(consentData); + adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); + adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); + adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); + + enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); + + // Assertions + expect(nextFnSpy.calledOnce).to.equal(true); + sinon.assert.calledWith(nextFnSpy, [{ + provider: 'analyticsAdapter_B', + options: {} + }, { + provider: 'analyticsAdapter_C', + options: {} + }]); + expect(logWarnSpy.calledOnce).to.equal(true); + }); + }); + + describe('validateRules', function () { + const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = []) => ({ + purpose: purposeName, + enforcePurpose: enforcePurpose, + enforceVendor: enforceVendor, + vendorExceptions: vendorExceptions + }); + + const consentData = { + vendorData: staticConfig.consentData.getTCData, + apiVersion: 2, + gdprApplies: true + }; + + // Bidder - 'bidderA' has vendorConsent + const vendorAllowedModule = 'bidderA'; + const vendorAllowedGvlId = 1; + + // Bidder = 'bidderB' doesn't have vendorConsent + const vendorBlockedModule = 'bidderB'; + const vendorBlockedGvlId = 3; + + const consentDataWithPurposeConsentFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalse.vendorData.purpose.consents['1'] = false; + + it('should return true when enforcePurpose=true AND purposeConsent[p]==true AND enforceVendor[p,v]==true AND vendorConsent[v]==true', function () { + // 'enforcePurpose' and 'enforceVendor' both are 'true' + const gdprRule = createGdprRule('storage', true, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + + // case 3 - Purpose consent is 'false' but vendor consent is 'true'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=true AND purposeConsent[p]==true AND enforceVendor[p,v]==false', function () { + // 'enforcePurpose' is 'true' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('storage', true, false, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'true' because vendorConsent doens't matter + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'false' but vendor consent is 'true'. validateRules must return 'false' because vendorConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' and vendorConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=false AND enforceVendor[p,v]==true AND vendorConsent[v]==true', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('storage', false, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true' + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'false' because purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + + // case 3 - urpose consent is 'false' but vendor consent is 'true'. validateRules must return 'true' because purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'false' and purposeConsent doesn't matter + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when enforcePurpose=false AND enforceVendor[p,v]==false', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('storage', false, false, []); + + // case 1 - Both purpose consent and vendor consent is 'true'. validateRules must return 'true', both the consents do not matter. + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - urpose consent is 'false' but vendor consent is 'true'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Both purpose consent and vendor consent is 'false'. validateRules must return 'true', both the consents do not matter. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + }); + + it('should return true when "vendorExceptions" contains the name of the vendor under test', function () { + // 'vendorExceptions' contains 'bidderB' which doesn't have vendor consent. + const gdprRule = createGdprRule('storage', false, true, [vendorBlockedModule]); + + /* 'bidderB' gets a free pass since it's included in the 'vendorExceptions' array. validateRules must disregard + user's choice for purpose and vendor consent and return 'true' for this bidder(s) */ + const isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + }); + + describe('Purpose 2 special case', function () { + const consentDataWithLIFalse = utils.deepClone(consentData); + consentDataWithLIFalse.vendorData.purpose.legitimateInterests['2'] = false; + + const consentDataWithPurposeConsentFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalse.vendorData.purpose.consents['2'] = false; + + const consentDataWithPurposeConsentFalseAndLIFalse = utils.deepClone(consentData); + consentDataWithPurposeConsentFalseAndLIFalse.vendorData.purpose.legitimateInterests['2'] = false; + consentDataWithPurposeConsentFalseAndLIFalse.vendorData.purpose.consents['2'] = false; + + it('should return true when (enforcePurpose=true AND purposeConsent[p]===true AND enforceVendor[p.v]===true AND vendorConsent[v]===true) OR (purposesLITransparency[p]===true)', function () { + // both 'enforcePurpose' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('basicAds', true, true, []); + + // case 1 - Both purpose consent and vendor consent is 'true', but legitimateInterests for purpose 2 is 'false'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'true' but vendor consent is 'false', but legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'true' and vendor consent is 'true', as well as legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 4 - Purpose consent is 'true' and vendor consent is 'false', and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when (enforcePurpose=true AND purposeConsent[p]===true AND enforceVendor[p.v]===false) OR (purposesLITransparency[p]===true)', function () { + // 'enforcePurpose' is 'true' and 'enforceVendor' is 'false' + const gdprRule = createGdprRule('basicAds', true, false, []); + + // case 1 - Purpose consent is 'true', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Purpose consent is 'false', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Purpose consent is 'false', vendor consent doesn't matter and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalseAndLIFalse, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(false); + }); + + it('should return true when (enforcePurpose=false AND enforceVendor[p,v]===true AND vendorConsent[v]===true) OR (purposesLITransparency[p]===true)', function () { + // 'enforcePurpose' is 'false' and 'enforceVendor' is 'true' + const gdprRule = createGdprRule('basicAds', false, true, []); + + // case - 1 Vendor consent is 'true', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + let isAllowed = validateRules(gdprRule, consentData, vendorAllowedModule, vendorAllowedGvlId); + expect(isAllowed).to.equal(true); + + // case 2 - Vendor consent is 'false', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'true'. validateRules must return 'true'. + isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(true); + + // case 3 - Vendor consent is 'false', purpose consent doesn't matter and legitimateInterests for purpose 2 is 'false'. validateRules must return 'false'. + isAllowed = validateRules(gdprRule, consentDataWithLIFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.equal(false); + }); + }); + }) + + describe('setEnforcementConfig', function () { + let sandbox; + const DEFAULT_RULES = [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }, { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }]; + beforeEach(function () { + sandbox = sinon.createSandbox(); + logWarnSpy = sandbox.spy(utils, 'logWarn'); + }); + afterEach(function () { + config.resetConfig(); + sandbox.restore(); + }); + + it('should enforce TCF2 Purpose1 and Purpose 2 if no "rules" found in the config', function () { + setEnforcementConfig({ + gdpr: { + cmpApi: 'iab', + allowAuctionWithoutConsent: true, + timeout: 5000 + } + }); + + expect(logWarnSpy.calledOnce).to.equal(true); + expect(enforcementRules).to.deep.equal(DEFAULT_RULES); + }); + + it('should enforce TCF2 Purpose 2 also if only Purpose 1 is defined in "rules"', function () { + const purpose1RuleDefinedInConfig = { + purpose: 'storage', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: ['bidderA'] + } + setEnforcementConfig({ + gdpr: { + rules: [purpose1RuleDefinedInConfig] + } + }); + + expect(purpose1Rule).to.deep.equal(purpose1RuleDefinedInConfig); + expect(purpose2Rule).to.deep.equal(DEFAULT_RULES[1]); + }); + + it('should enforce TCF2 Purpose 1 also if only Purpose 2 is defined in "rules"', function () { + const purpose2RuleDefinedInConfig = { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: true, + vendorExceptions: ['bidderA'] + } + setEnforcementConfig({ + gdpr: { + rules: [purpose2RuleDefinedInConfig] + } + }); + + expect(purpose1Rule).to.deep.equal(DEFAULT_RULES[0]); + expect(purpose2Rule).to.deep.equal(purpose2RuleDefinedInConfig); + }); + + it('should use the "rules" defined in config if a definition found', function() { + const rules = [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false + }, { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: false }] - expect(nextFnSpy.calledWith(undefined, expectedSubmodules, consentData)); + setEnforcementConfig({gdpr: { rules }}); + + expect(enforcementRules).to.deep.equal(rules); + }); + }); + + describe('TCF2FinalResults', function() { + let sandbox; + beforeEach(function() { + sandbox = sinon.createSandbox(); + sandbox.spy(events, 'emit'); + }); + afterEach(function() { + config.resetConfig(); + sandbox.restore(); }); + it('should emit TCF2 enforcement data on auction end', function() { + const rules = [{ + purpose: 'storage', + enforcePurpose: false, + enforceVendor: false + }, { + purpose: 'basicAds', + enforcePurpose: false, + enforceVendor: false + }] + setEnforcementConfig({gdpr: { rules }}); + + events.emit('auctionEnd', {}) + + // Assertions + sinon.assert.calledWith(events.emit.getCall(1), 'tcf2Enforcement', sinon.match.object); + }) }); }); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js new file mode 100644 index 00000000000..16b84467af2 --- /dev/null +++ b/test/spec/modules/gptPreAuction_spec.js @@ -0,0 +1,191 @@ +import { + appendGptSlots, + appendPbAdSlot, + _currentConfig, + makeBidRequestsHook +} from 'modules/gptPreAuction.js'; +import { config } from 'src/config.js'; +import { makeSlot } from '../integration/faker/googletag.js'; + +describe('GPT pre-auction module', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + config.setConfig({ gptPreAuction: { enabled: false } }); + }); + + const testSlots = [ + makeSlot({ code: 'slotCode1', divId: 'div1' }), + makeSlot({ code: 'slotCode2', divId: 'div2' }), + makeSlot({ code: 'slotCode3', divId: 'div3' }) + ]; + + describe('appendPbAdSlot', () => { + // sets up our document body to test the pbAdSlot dom actions against + document.body.innerHTML = '
test1
' + + '
test2
' + + '
test2
'; + + it('should be unchanged if already defined on adUnit', () => { + const adUnit = { fpd: { context: { pbAdSlot: '12345' } } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('12345'); + }); + + it('should use adUnit.code if matching id exists', () => { + const adUnit = { code: 'foo1', fpd: { context: {} } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('bar1'); + }); + + it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, fpd: { context: { adServer: { name: 'gam', adSlot: '/baz' } } } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('/baz'); + }); + + it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, fpd: { context: {} } }; + adUnit.code = 'foo5'; + appendPbAdSlot(adUnit, undefined); + expect(adUnit.fpd.context.pbAdSlot).to.equal('foo5'); + }); + + it('should use the adUnit.code if all other sources failed', () => { + const adUnit = { code: 'foo4', fpd: { context: {} } }; + appendPbAdSlot(adUnit, undefined); + expect(adUnit.fpd.context.pbAdSlot).to.equal('foo4'); + }); + + it('should use the customPbAdSlot function if one is given', () => { + config.setConfig({ + gptPreAuction: { + customPbAdSlot: () => 'customPbAdSlotName' + } + }); + + const adUnit = { code: 'foo1', fpd: { context: {} } }; + appendPbAdSlot(adUnit); + expect(adUnit.fpd.context.pbAdSlot).to.equal('customPbAdSlotName'); + }); + }); + + describe('appendGptSlots', () => { + it('should not add adServer object to context if no slots defined', () => { + const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.undefined; + }); + + it('should not add adServer object to context if no slot matches', () => { + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.undefined; + }); + + it('should add adServer object to context if matching slot is found', () => { + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'slotCode2', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.an('object'); + expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode2' }); + }); + + it('should use the customGptSlotMatching function if one is given', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: slot => + adUnitCode => adUnitCode.toUpperCase() === slot.getAdUnitPath().toUpperCase() + } + }); + + window.googletag.pubads().setSlots(testSlots); + const adUnit = { code: 'SlOtCoDe1', fpd: { context: {} } }; + appendGptSlots([adUnit]); + expect(adUnit.fpd.context.adServer).to.be.an('object'); + expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode1' }); + }); + }); + + describe('handleSetGptConfig', () => { + it('should enable the module by default', () => { + config.setConfig({ gptPreAuction: {} }); + expect(_currentConfig.enabled).to.equal(true); + }); + + it('should disable the module if told to in set config', () => { + config.setConfig({ gptPreAuction: { enabled: false } }); + expect(_currentConfig).to.be.an('object').that.is.empty; + }); + + it('should accept custom functions in config', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: () => 'customGptSlot', + customPbAdSlot: () => 'customPbAdSlot' + } + }); + + expect(_currentConfig.enabled).to.equal(true); + expect(_currentConfig.customGptSlotMatching).to.a('function'); + expect(_currentConfig.customPbAdSlot).to.a('function'); + expect(_currentConfig.customGptSlotMatching()).to.equal('customGptSlot'); + expect(_currentConfig.customPbAdSlot()).to.equal('customPbAdSlot'); + }); + + it('should check that custom functions in config are type function', () => { + config.setConfig({ + gptPreAuction: { + customGptSlotMatching: 12345, + customPbAdSlot: 'test' + } + }); + expect(_currentConfig).to.deep.equal({ + enabled: true, + customGptSlotMatching: false, + customPbAdSlot: false + }); + }); + }); + + describe('makeBidRequestsHook', () => { + let returnedAdUnits; + const runMakeBidRequests = adUnits => { + const next = adUnits => { + returnedAdUnits = adUnits; + }; + makeBidRequestsHook(next, adUnits); + }; + + it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { + const testAdUnits = [{ + code: 'adUnit1', + fpd: { context: { pbAdSlot: '12345' } } + }, { + code: 'slotCode1', + fpd: { context: { pbAdSlot: '67890' } } + }, { + code: 'slotCode3', + }]; + + const expectedAdUnits = [{ + code: 'adUnit1', + fpd: { context: { pbAdSlot: '12345' } } + }, { + code: 'slotCode1', + fpd: { context: { pbAdSlot: '67890', adServer: { name: 'gam', adSlot: 'slotCode1' } } } + }, { + code: 'slotCode3', + fpd: { context: { pbAdSlot: 'slotCode3', adServer: { name: 'gam', adSlot: 'slotCode3' } } } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + }); +}); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index c8d04113aeb..344f1764c05 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -103,7 +103,7 @@ describe('TheMediaGrid Adapter', function () { ]; it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -115,7 +115,7 @@ describe('TheMediaGrid Adapter', function () { }); it('sizes must be added from mediaTypes', function () { - const request = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); + const [request] = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -125,7 +125,7 @@ describe('TheMediaGrid Adapter', function () { }); it('sizes must not be duplicated', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); + const [request] = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -135,7 +135,7 @@ describe('TheMediaGrid Adapter', function () { }); it('if gdprConsent is present payload must have gdpr params', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); + const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); @@ -144,7 +144,7 @@ describe('TheMediaGrid Adapter', function () { }); it('if gdprApplies is false gdpr_applies must be 0', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -152,7 +152,7 @@ describe('TheMediaGrid Adapter', function () { }); it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -161,7 +161,7 @@ describe('TheMediaGrid Adapter', function () { it('if usPrivacy is present payload must have us_privacy param', function () { const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + const [request] = spec.buildRequests(bidRequests, bidderRequestWithUSP); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('us_privacy', '1YNN'); @@ -188,7 +188,7 @@ describe('TheMediaGrid Adapter', function () { } ); - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + const [request] = spec.buildRequests(bidRequestWithKeywords, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.keywords).to.be.an('string'); @@ -235,7 +235,7 @@ describe('TheMediaGrid Adapter', function () { } ); - const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + const [request] = spec.buildRequests(bidRequestWithKeywords, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.keywords).to.be.an('string'); @@ -262,6 +262,324 @@ describe('TheMediaGrid Adapter', function () { }); }); + describe('buildRequests in new format', function () { + function parseRequest(data) { + return JSON.parse(data); + } + const bidderRequest = { + refererInfo: {referer: 'https://example.com'}, + bidderRequestId: '22edbae2733bf6', + auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + timeout: 3000 + }; + const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + let bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '11', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]], + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '3', + 'useNewFormat': true + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [[400, 600]] + }, + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + } + ]; + + it('should attach valid params to the tag', function () { + const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + }); + + it('make possible to process request without mediaTypes', function () { + const [request] = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }] + }); + }); + + it('should attach valid params to the video tag', function () { + const [request] = spec.buildRequests(bidRequests.slice(0, 3), bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'] + } + }] + }); + }); + + it('should support mixed mediaTypes', function () { + const [request] = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + } + }, { + 'id': bidRequests[3].bidId, + 'tagid': bidRequests[3].params.uid, + 'ext': {'divid': bidRequests[3].adUnitCode}, + 'banner': { + 'w': 728, + 'h': 90, + 'format': [{'w': 728, 'h': 90}] + }, + 'video': { + 'w': 400, + 'h': 600 + } + }] + }); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); + const [request] = spec.buildRequests(bidRequests, gdprBidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext).to.have.property('consent', 'AAA'); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const [request] = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('regs'); + expect(payload.regs).to.have.property('ext'); + expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); + }); + + it('if userId is present payload must have user.ext param with right keys', function () { + const bidRequestsWithUserIds = bidRequests.map((bid) => { + return Object.assign({ + userId: { + id5id: 'id5id_1', + tdid: 'tdid_1', + digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + lipb: {lipbid: 'lipb_1'} + } + }, bid); + }); + const [request] = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext).to.have.property('unifiedid', 'tdid_1'); + expect(payload.user.ext).to.have.property('id5id', 'id5id_1'); + expect(payload.user.ext).to.have.property('digitrustid', 'DTID'); + expect(payload.user.ext).to.have.property('liveintentid', 'lipb_1'); + }); + + it('if schain is present payload must have source.ext.schain param', function () { + const schain = { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + }; + const bidRequestsWithSChain = bidRequests.map((bid) => { + return Object.assign({ + schain: schain + }, bid); + }); + const [request] = spec.buildRequests(bidRequestsWithSChain, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('source'); + expect(payload.source).to.have.property('ext'); + expect(payload.source.ext).to.have.property('schain'); + expect(payload.source.ext.schain).to.deep.equal(schain); + }); + }); + describe('interpretResponse', function () { const responses = [ {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, @@ -288,7 +606,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1cbd2feafe5e8b', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -346,7 +664,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1fa09aee5c8c99', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '300bfeb0d71a5b', @@ -435,7 +753,7 @@ describe('TheMediaGrid Adapter', function () { {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'} ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -496,7 +814,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1fa09aee5c84d34', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); expect(result.length).to.equal(0); }); @@ -566,7 +884,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '32a1f276cb87cb8', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '2164be6358b9', @@ -670,7 +988,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '35bcbc0f7e79c', } ]; - const request = spec.buildRequests(bidRequests); + const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '5126e301f4be', diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 7022f8f96dd..af929f437da 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -114,6 +114,53 @@ describe('gumgumAdapter', function () { } } ]; + const vidMediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 123456, + protocols: [1, 2] + } + }; + + it('should return a defined sizes field for video', function () { + const request = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.sizes).to.equal(vidMediaTypes.video.playerSize); + }); + + describe('floorModule', function () { + const floorTestData = { + 'currency': 'USD', + 'floor': 1.50 + }; + bidRequests[0].getFloor = _ => { + return floorTestData; + }; + it('should return the value from getFloor if present', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(floorTestData.floor); + }); + it('should return the getFloor.floor value if it is greater than bidfloor', function () { + const bidfloor = 0.80; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(floorTestData.floor); + }); + it('should return the bidfloor value if it is greater than getFloor.floor', function () { + const bidfloor = 1.80; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.fp).to.equal(bidfloor); + }); + }); it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests)[0]; @@ -145,21 +192,7 @@ describe('gumgumAdapter', function () { expect(bidRequest.data).to.not.include.any.keys('t'); }); it('should send pubId if videoPubID param is specified', function () { - const mediaTypes = { - video: { - playerSize: [640, 480], - context: 'instream', - minduration: 1, - maxduration: 2, - linearity: 1, - startdelay: 1, - placement: 123456, - protocols: [1, 2] - } - }; - const request = Object.assign({}, bidRequests[0]); - request.mediaTypes = mediaTypes - request.params = { 'videoPubID': 123 }; + const request = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.include.any.keys('pubId'); expect(bidRequest.data.pubId).to.equal(request.params.videoPubID); @@ -423,7 +456,7 @@ describe('gumgumAdapter', function () { }); it('updates jcsi object when the server response jcsi prop is found', function () { - const response = Object.assign({cw: 'AD_JSON'}, serverResponse); + const response = Object.assign({ cw: 'AD_JSON' }, serverResponse); const bidResponse = spec.interpretResponse({ body: response }, bidRequest)[0].ad; const decodedResponse = JSON.parse(atob(bidResponse)); expect(decodedResponse.jcsi).to.eql(JCSI); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 680ffdc0fcc..63b04077f4e 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -5,7 +5,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/ixBidAdapter.js'; describe('IndexexchangeAdapter', function () { - const IX_SECURE_ENDPOINT = 'https://as-sec.casalemedia.com/cygnus'; + const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; @@ -110,6 +110,35 @@ describe('IndexexchangeAdapter', function () { ] }; + const DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN = { + cur: 'USD', + id: '11a22b33c44d', + seatbid: [ + { + bid: [ + { + crid: '12345', + adid: '14851455', + impid: '1a2b3c4d', + cid: '3051266', + price: 100, + w: 300, + h: 250, + id: '1', + ext: { + dspid: 50, + pricelevel: '_100', + advbrandid: 303325, + advbrand: 'OECTA' + }, + adm: '
' + } + ], + seat: '3970' + } + ] + }; + const DEFAULT_VIDEO_BID_RESPONSE = { cur: 'USD', id: '1aa2bb3cc4de', @@ -1102,7 +1131,8 @@ describe('IndexexchangeAdapter', function () { meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -1110,6 +1140,31 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should get correct bid response for banner ad with missing adomain', function () { + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + netRevenue: true, + dealId: undefined, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA' + } + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + it('should set creativeId to default value if not provided', function () { const bidResponse = utils.deepClone(DEFAULT_BANNER_BID_RESPONSE); delete bidResponse.seatbid[0].bid[0].crid; @@ -1129,7 +1184,8 @@ describe('IndexexchangeAdapter', function () { meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -1155,7 +1211,8 @@ describe('IndexexchangeAdapter', function () { meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -1182,7 +1239,8 @@ describe('IndexexchangeAdapter', function () { meta: { networkId: 50, brandId: 303325, - brandName: 'OECTA' + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] } } ]; @@ -1207,7 +1265,8 @@ describe('IndexexchangeAdapter', function () { meta: { networkId: 51, brandId: 303326, - brandName: 'OECTB' + brandName: 'OECTB', + advertiserDomains: ['www.abcd.com'] } } ]; diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js index 09d997e9349..ee478acbc83 100644 --- a/test/spec/modules/mediaforceBidAdapter_spec.js +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -1,6 +1,7 @@ import {assert} from 'chai'; import {spec} from 'modules/mediaforceBidAdapter.js'; import * as utils from '../../../src/utils.js'; +import {BANNER, NATIVE} from '../../../src/mediaTypes.js'; describe('mediaforce bid adapter', function () { let sandbox; @@ -19,7 +20,7 @@ describe('mediaforce bid adapter', function () { } const language = getLanguage(); - const baseUrl = 'https://rtb.mfadsrvr.com' + const baseUrl = 'https://rtb.mfadsrvr.com'; describe('isBidRequestValid()', function () { const defaultBid = { @@ -56,17 +57,6 @@ describe('mediaforce bid adapter', function () { bid.params = {publisher_id: 2, placement_id: '123'}; assert.equal(spec.isBidRequestValid(bid), true); }); - - it('should return false when mediaTypes == native passed (native is not supported yet)', function () { - let bid = utils.deepClone(defaultBid); - bid.mediaTypes = { - native: { - sizes: [[300, 250]] - } - }; - bid.params = {publisher_id: 2, placement_id: '123'}; - assert.equal(spec.isBidRequestValid(bid), true); - }); }); describe('buildRequests()', function () { @@ -76,9 +66,35 @@ describe('mediaforce bid adapter', function () { publisher_id: 'pub123', placement_id: '202', }, + nativeParams: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [300, 250], + }, + sponsoredBy: { + required: true + } + }, mediaTypes: { banner: { sizes: [[300, 250]] + }, + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + sizes: [300, 250], + }, + sponsoredBy: { + required: true + } } }, transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', @@ -95,7 +111,7 @@ describe('mediaforce bid adapter', function () { const requestUrl = `${baseUrl}/header_bid`; const dnt = utils.getDNT() ? 1 : 0; - const secure = 1 + const secure = 1; it('should return undefined if no validBidRequests passed', function () { assert.equal(spec.buildRequests([]), undefined); @@ -135,13 +151,26 @@ describe('mediaforce bid adapter', function () { secure: secure, bidfloor: bid.params.bidfloor, banner: {w: 300, h: 250}, + native: { + ver: '1.2', + request: { + assets: [ + {id: 1, title: {len: 800}, required: 1}, + {id: 3, img: {w: 300, h: 250, type: 3}, required: 1}, + {id: 5, data: {type: 1}, required: 1} + ], + context: 1, + plcmttype: 1, + ver: '1.2' + } + }, }], }); assert.deepEqual(request, { method: 'POST', url: requestUrl, - data: '{"id":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b","site":{"page":"https%3A%2F%2Fwww.prebid.org","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"imp":[{"tagid":"202","secure":1,"bidfloor":0.5,"banner":{"w":300,"h":250}}]}', + data: '{"id":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b","site":{"page":"https%3A%2F%2Fwww.prebid.org","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"imp":[{"tagid":"202","secure":1,"bidfloor":0.5,"banner":{"w":300,"h":250},"native":{"ver":"1.2","request":{"assets":[{"required":1,"id":1,"title":{"len":800}},{"required":1,"id":3,"img":{"type":3,"w":300,"h":250}},{"required":1,"id":5,"data":{"type":1}}],"context":1,"plcmttype":1,"ver":"1.2"}}}]}', }); }); @@ -173,6 +202,7 @@ describe('mediaforce bid adapter', function () { cid: '2_ssl', h: 100, cat: ['IAB1-1'], + dealid: '3901521', crid: '2_ssl', impid: '2b3c9d103723a7', adid: '2_ssl', @@ -193,11 +223,13 @@ describe('mediaforce bid adapter', function () { assert.deepEqual(bids, ([{ ad: bid.adm, cpm: bid.price, + dealId: bid.dealid, creativeId: bid.adid, currency: response.body.cur, height: bid.h, netRevenue: true, burl: bid.burl, + mediaType: BANNER, requestId: bid.impid, ttl: 300, width: bid.w, @@ -205,6 +237,174 @@ describe('mediaforce bid adapter', function () { }); }); + describe('interpretResponse() native as object', function () { + it('successfull response', function () { + let titleText = 'Colorado Drivers With No DUI\'s Getting A Pay Day on Friday'; + let imgData = { + url: `${baseUrl}/image`, + w: 1200, + h: 627 + }; + let nativeLink = `${baseUrl}/click/`; + let nativeTracker = `${baseUrl}/imp-image`; + let sponsoredByValue = 'Comparisons.org'; + let bodyValue = 'Drivers With No Tickets In 3 Years Should Do This On June'; + let bid = { + price: 3, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + cat: ['IAB1-1'], + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + ext: { + advertiser_name: 'MediaForce', + native: { + link: {url: nativeLink}, + assets: [{ + id: 1, + title: {text: titleText}, + required: 1 + }, { + id: 3, + img: imgData + }, { + id: 5, + data: {value: sponsoredByValue} + }, { + id: 4, + data: {value: bodyValue} + }], + imptrackers: [nativeTracker], + ver: '1' + }, + language: 'en', + agency_name: 'MediaForce DSP' + } + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + native: { + clickUrl: nativeLink, + clickTrackers: [], + impressionTrackers: [nativeTracker], + javascriptTrackers: [], + title: titleText, + image: { + url: imgData.url, + width: imgData.w, + height: imgData.h + }, + sponsoredBy: sponsoredByValue, + body: bodyValue + }, + cpm: bid.price, + creativeId: bid.adid, + currency: response.body.cur, + netRevenue: true, + burl: bid.burl, + mediaType: NATIVE, + requestId: bid.impid, + ttl: 300, + }])); + }); + }); + + describe('interpretResponse() native as string', function () { + it('successfull response', function () { + let titleText = 'Colorado Drivers With No DUI\'s Getting A Pay Day on Friday'; + let imgData = { + url: `${baseUrl}/image`, + w: 1200, + h: 627 + }; + let nativeLink = `${baseUrl}/click/`; + let nativeTracker = `${baseUrl}/imp-image`; + let sponsoredByValue = 'Comparisons.org'; + let bodyValue = 'Drivers With No Tickets In 3 Years Should Do This On June'; + let adm = JSON.stringify({ + native: { + link: {url: nativeLink}, + assets: [{ + id: 1, + title: {text: titleText}, + required: 1 + }, { + id: 3, + img: imgData + }, { + id: 5, + data: {value: sponsoredByValue} + }, { + id: 4, + data: {value: bodyValue} + }], + imptrackers: [nativeTracker], + ver: '1' + } + }); + let bid = { + price: 3, + id: '65599d0a-42d2-446a-9d39-6086c1433ffe', + burl: `${baseUrl}/burl/\${AUCTION_PRICE}`, + cid: '2_ssl', + cat: ['IAB1-1'], + crid: '2_ssl', + impid: '2b3c9d103723a7', + adid: '2_ssl', + adm: adm + }; + + let response = { + body: { + seatbid: [{ + bid: [bid] + }], + cur: 'USD', + id: '620190c2-7eef-42fa-91e2-f5c7fbc2bdd3' + } + }; + + let bids = spec.interpretResponse(response); + assert.deepEqual(bids, ([{ + native: { + clickUrl: nativeLink, + clickTrackers: [], + impressionTrackers: [nativeTracker], + javascriptTrackers: [], + title: titleText, + image: { + url: imgData.url, + width: imgData.w, + height: imgData.h + }, + sponsoredBy: sponsoredByValue, + body: bodyValue + }, + cpm: bid.price, + creativeId: bid.adid, + currency: response.body.cur, + netRevenue: true, + burl: bid.burl, + mediaType: NATIVE, + requestId: bid.impid, + ttl: 300, + }])); + }); + }); + describe('onBidWon()', function () { beforeEach(function() { sinon.stub(utils, 'triggerPixel'); diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index dcec1050652..97b45cef00c 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -10,8 +10,10 @@ const { const MOCK = { AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739}, + AUCTION_INIT_WITH_FLOOR: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'bidderRequests': [{'bids': [{ 'floorData': {'enforcements': {'enforceJS': true}} }]}]}, BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, - BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, + MULTI_FORMAT_BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'video': {'playerSize': [640, 480], 'context': 'instream'}, 'native': {'image': {'required': true, 'sizes': [150, 50]}, 'title': {'required': true, 'len': 80}}, 'ext': ['asdads']}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, + BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, AUCTION_END: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'auctionEnd': 1584563605739}, SET_TARGETING: {'div-gpt-ad-1460505748561-0': {'prebid_test': '1', 'hb_format': 'banner', 'hb_source': 'client', 'hb_size': '300x250', 'hb_pb': '2.00', 'hb_adid': '3e6e4bce5c8fb3', 'hb_bidder': 'medianet', 'hb_format_medianet': 'banner', 'hb_source_medianet': 'client', 'hb_size_medianet': '300x250', 'hb_pb_medianet': '2.00', 'hb_adid_medianet': '3e6e4bce5c8fb3', 'hb_bidder_medianet': 'medianet'}}, BID_WON: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, @@ -19,6 +21,15 @@ const MOCK = { BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}] } +function performAuctionWithFloorConfig() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT_WITH_FLOOR); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON); +} + function performStandardAuctionWithWinner() { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -28,6 +39,14 @@ function performStandardAuctionWithWinner() { events.emit(BID_WON, MOCK.BID_WON); } +function performMultiFormatAuctionWithNoBid() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.MULTI_FORMAT_BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + function performStandardAuctionWithNoBid() { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -115,6 +134,17 @@ describe('Media.net Analytics Adapter', function() { expect(medianetAnalytics.getlogsQueue().length).to.equal(0); }); + it('should have all applicable sizes in request', function() { + medianetAnalytics.clearlogsQueue(); + performMultiFormatAuctionWithNoBid(); + const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + medianetAnalytics.clearlogsQueue(); + + expect(noBidLog.szs).to.equal(encodeURIComponent('300x250|1x1|640x480')); + expect(noBidLog.vplcmtt).to.equal('instream'); + expect(noBidLog.sz2).to.equal(encodeURIComponent('300x250')); + }); + it('should have winner log in standard auction', function() { medianetAnalytics.clearlogsQueue(); performStandardAuctionWithWinner(); @@ -142,7 +172,24 @@ describe('Media.net Analytics Adapter', function() { ogbdp: '1.1495', flt: '1', supcrid: 'div-gpt-ad-1460505748561-0', - mpvid: '123' + mpvid: '123', + bidflr: '1.1' + }); + }); + + it('should have correct bid floor data in winner log', function() { + medianetAnalytics.clearlogsQueue(); + performAuctionWithFloorConfig(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + medianetAnalytics.clearlogsQueue(); + + expect(winnerLog[0]).to.include({ + winner: '1', + curr: 'USD', + ogbdp: '1.1495', + bidflr: '1.1', + flrrule: 'banner', + flrdata: encodeURIComponent('ln=||skp=||enfj=true||enfd=||sr=||fs=') }); }); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index fcfdcdf8c2f..649929056fa 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -37,6 +37,11 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -81,6 +86,11 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -127,6 +137,11 @@ let VALID_BID_REQUEST = [{ }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'sizes': [[300, 251]], 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', @@ -731,6 +746,28 @@ let VALID_BID_REQUEST = [{ }], 'tmax': config.getConfig('bidderTimeout') }, + + VALID_VIDEO_BID_REQUEST = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'video': { + 'skipppable': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'mediaTypes': { + 'video': { + 'context': 'instream', + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }], + VALID_PAYLOAD_PAGE_META = (() => { let PAGE_META; try { @@ -1156,6 +1193,11 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_NATIVE); }); + it('should parse params for video request', function () { + let bidReq = spec.buildRequests(VALID_VIDEO_BID_REQUEST, VALID_AUCTIONDATA); + expect(JSON.stringify(bidReq.data)).to.include('instream'); + }); + it('should have valid crid present in bid request', function() { sandbox.stub(config, 'getConfig').callsFake((key) => { const config = { diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index a951c74b20b..c1462c3814d 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -132,10 +132,10 @@ describe('onetag', function () { const data = JSON.parse(d); it('Should contain all keys', function () { expect(data).to.be.an('object'); - expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'onetagSid'); + expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'timing', 'version'); expect(data.location).to.be.a('string'); - expect(data.masked).to.be.a('number'); - expect(data.referrer).to.be.a('string'); + expect(data.masked).to.be.oneOf([0, 1, 2]); + expect(data.referrer).to.satisfy(referrer => referrer === null || typeof referrer === 'string'); expect(data.sHeight).to.be.a('number'); expect(data.sWidth).to.be.a('number'); expect(data.wWidth).to.be.a('number'); @@ -148,6 +148,7 @@ describe('onetag', function () { expect(data.sTop).to.be.a('number'); expect(data.hLength).to.be.a('number'); expect(data.bids).to.be.an('array'); + expect(data.version).to.have.all.keys('prebid', 'adapter'); const bids = data['bids']; for (let i = 0; i < bids.length; i++) { const bid = bids[i]; diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index a6fbc9666b9..0808d78c0aa 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -290,6 +290,38 @@ describe('OpenxAdapter', function () { expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); }); + + describe('and request config uses test', () => { + const videoBidWithTest = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + test: true + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + let mockBidderRequest = {refererInfo: {}}; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithTest)).to.equal(true); + }); + + it('should send video bid request to openx url via GET, with vtest=1 video parameter', function () { + const request = spec.buildRequests([videoBidWithTest], mockBidderRequest); + expect(request[0].data.vtest).to.equal(1); + }); + }); }); }); @@ -508,25 +540,6 @@ describe('OpenxAdapter', function () { expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); }); - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); - it('should send out custom bc parameter, if override is present', function () { const bidRequest = Object.assign({}, bidRequestsWithMediaTypes[0], @@ -1034,7 +1047,7 @@ describe('OpenxAdapter', function () { idl_env: '1111-idl_env', lipb: {lipbid: '1111-lipb'}, netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - parrableid: 'eidVersion.encryptionKeyReference.encryptedValue', + parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, pubcid: '1111-pubcid', tdid: '1111-tdid', }; @@ -1080,6 +1093,9 @@ describe('OpenxAdapter', function () { case 'lipb': userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; break; + case 'parrableId': + userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; + break; default: userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; } @@ -1089,6 +1105,107 @@ describe('OpenxAdapter', function () { }); }); }); + + describe('floors', function () { + it('should send out custom floors on bids that have customFloors specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.500001 + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); + }); + + context('with floors module', function () { + let adServerCurrencyStub; + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids', function () { + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'AUS', + floor: 9.99 + } + } + } + ); + + const bidRequest2 = Object.assign({}, + bidRequestsWithMediaTypes[1], + { + getFloor: () => { + return { + currency: 'AUS', + floor: 18.881 + } + } + } + ); + + const request = spec.buildRequests([bidRequest1, bidRequest2], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('9990,18881'); + }); + + it('should send out floors on bids in the default currency', function () { + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return {}; + } + } + ); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + }); + + it('should send out floors on bids in the ad server currency if defined', function () { + adServerCurrencyStub.returns('bitcoin'); + + const bidRequest1 = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return {}; + } + } + ); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + }); + }) + }) }); describe('buildRequests for video', function () { @@ -1126,6 +1243,11 @@ describe('OpenxAdapter', function () { expect(dataParams.vwd).to.equal(640); }); + it('shouldn\'t have the test parameter', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.vtest).to.be.undefined; + }); + it('should send a bc parameter', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); const dataParams = request[0].data; diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index eec6ccd19f6..df551311c0b 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -1,8 +1,6 @@ import {expect} from 'chai'; import {spec} from 'modules/orbidderBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; -import openxAdapter from '../../../modules/openxAnalyticsAdapter.js'; -import {detectReferer} from 'src/refererDetection.js'; describe('orbidderBidAdapter', () => { const adapter = newBidder(spec); @@ -93,7 +91,7 @@ describe('orbidderBidAdapter', () => { it('sends bid request to endpoint via https using post', () => { expect(request.method).to.equal('POST'); expect(request.url.indexOf('https://')).to.equal(0); - expect(request.url).to.equal(`${spec.orbidderHost}/bid`); + expect(request.url).to.equal(`${spec.hostname}/bid`); }); it('contains prebid version parameter', () => { diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index c8a636efb4c..f20e75dfedd 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -66,7 +66,7 @@ var validBidRequestsWithUserIdData = [ params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87', - userId: {'pubcid': '12345678', 'id5id': 'ID5-someId', 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableid': 'parrableid123'} + userId: {'pubcid': '12345678', 'id5id': 'ID5-someId', 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableId': {eid: 'parrableid123'}} } ]; var validBidRequestsMinimal = [ @@ -2121,7 +2121,7 @@ describe('ozone Adapter', function () { 'id5id': '2222', 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', + 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, 'pubcid': '5555', 'tdid': '6666' }; @@ -2141,7 +2141,7 @@ describe('ozone Adapter', function () { 'id5id': '2222', 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', + 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, // 'pubcid': '5555', // remove pubcid from here to emulate the OLD module & cause the failover code to kick in 'tdid': '6666' }; diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index fdfd3042561..7db22af82ab 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import { newStorageManager } from 'src/storageManager.js'; @@ -189,11 +190,11 @@ describe('Parrable ID System', function() { let eid = '01.123.4567890'; let parrableId = { eid, - ccpaOptout: true + ibaOptout: true }; expect(parrableIdSubmodule.decode(parrableId)).to.deep.equal({ - parrableid: eid + parrableId }); }); }); @@ -203,7 +204,7 @@ describe('Parrable ID System', function() { beforeEach(function() { adUnits = [getAdUnitMock()]; - writeParrableCookie({ eid: P_COOKIE_EID }); + writeParrableCookie({ eid: P_COOKIE_EID, ibaOptout: true }); setSubmoduleRegistry([parrableIdSubmodule]); init(config); config.setConfig(getConfigMock()); @@ -218,8 +219,47 @@ describe('Parrable ID System', function() { requestBidsHook(function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.parrableid'); - expect(bid.userId.parrableid).to.equal(P_COOKIE_EID); + expect(bid).to.have.deep.nested.property('userId.parrableId'); + expect(bid.userId.parrableId.eid).to.equal(P_COOKIE_EID); + expect(bid.userId.parrableId.ibaOptout).to.equal(true); + const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); + expect(parrableIdAsEid).to.deep.equal({ + source: 'parrable.com', + uids: [{ + id: P_COOKIE_EID, + atype: 1, + ext: { + ibaOptout: true + } + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + + it('supplies an optout reason when the EID is missing due to CCPA non-consent', function(done) { + // the ID system itself will not write a cookie with an EID when CCPA=true + writeParrableCookie({ ccpaOptout: true }); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.parrableId'); + expect(bid.userId.parrableId).to.not.have.property('eid'); + expect(bid.userId.parrableId.ccpaOptout).to.equal(true); + const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); + expect(parrableIdAsEid).to.deep.equal({ + source: 'parrable.com', + uids: [{ + id: '', + atype: 1, + ext: { + ccpaOptout: true + } + }] + }); }); }); done(); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 294ada6f988..5abe068c100 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -547,6 +547,37 @@ describe('S2S Adapter', function () { expect(requestBid.user).to.not.exist; }); + it('adds additional consent information to ortb2 request depending on presence of module', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + + let gdprBidRequest = utils.deepClone(BID_REQUESTS); + gdprBidRequest[0].gdprConsent = { + consentString: 'abc123', + addtlConsent: 'superduperconsent', + gdprApplies: true + }; + + adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.regs.ext.gdpr).is.equal(1); + expect(requestBid.user.ext.consent).is.equal('abc123'); + expect(requestBid.user.ext.ConsentedProvidersSettings.consented_providers).is.equal('superduperconsent'); + + config.resetConfig(); + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(server.requests[1].requestBody); + + expect(requestBid.regs).to.not.exist; + expect(requestBid.user).to.not.exist; + }); + it('check gdpr info gets added into cookie_sync request: have consent data', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; @@ -712,40 +743,6 @@ describe('S2S Adapter', function () { }); }); - it('adds digitrust id is present and user is not optout', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { s2sConfig: ortb2Config }; - config.setConfig(consentConfig); - - let digiTrustObj = { - privacy: { - optout: false - }, - id: 'testId', - keyv: 'testKeyV' - }; - - let digiTrustBidRequest = utils.deepClone(BID_REQUESTS); - digiTrustBidRequest[0].bids[0].userId = { digitrustid: { data: digiTrustObj } }; - - adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(server.requests[0].requestBody); - - expect(requestBid.user.ext.digitrust).to.deep.equal({ - id: digiTrustObj.id, - keyv: digiTrustObj.keyv - }); - - digiTrustObj.privacy.optout = true; - - adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); - requestBid = JSON.parse(server.requests[1].requestBody); - - expect(requestBid.user && request.user.ext && requestBid.user.ext.digitrust).to.not.exist; - }); - it('adds device and app objects to request', function () { const _config = { s2sConfig: CONFIG, @@ -1160,7 +1157,7 @@ describe('S2S Adapter', function () { criteoId: '44VmRDeUE3ZGJ5MzRkRVJHU3BIUlJ6TlFPQUFU', tdid: 'abc123', pubcid: '1234', - parrableid: '01.1563917337.test-eid', + parrableId: { eid: '01.1563917337.test-eid' }, lipb: { lipbid: 'li-xyz', segments: ['segA', 'segB'] @@ -1445,6 +1442,80 @@ describe('S2S Adapter', function () { }); describe('pbAdSlot config', function () { + it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + }); + + it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = {}; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + }); + + it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = { + context: { + pbAdSlot: '' + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + }); + + it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { + const ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + const consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].fpd = { + context: { + pbAdSlot: '/a/b/c' + } + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp).to.be.a('array'); + expect(parsedRequestBody.imp[0]).to.be.a('object'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.context.data.pbadslot).to.equal('/a/b/c'); + }); + }); + + describe('GAM ad unit config', function () { it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context\" is undefined', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; @@ -1460,7 +1531,7 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); }); - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { + it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" is undefined', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; const consentConfig = { s2sConfig: ortb2Config }; @@ -1476,7 +1547,7 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); }); - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { + it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" is empty string', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; const consentConfig = { s2sConfig: ortb2Config }; @@ -1484,7 +1555,9 @@ describe('S2S Adapter', function () { const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = { context: { - pbAdSlot: '' + adServer: { + adSlot: '' + } } }; @@ -1496,7 +1569,7 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); }); - it('should send \"imp.ext.context.data.adslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { + it('should send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" value is a non-empty string', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; const consentConfig = { s2sConfig: ortb2Config }; @@ -1504,7 +1577,9 @@ describe('S2S Adapter', function () { const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = { context: { - pbAdSlot: '/a/b/c' + adServer: { + adSlot: '/a/b/c' + } } }; diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 9d35554c27f..0a006e0eb48 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -314,9 +314,10 @@ describe('the price floors module', function () { validateBidRequests(false, { skipped: true, modelVersion: undefined, - location: undefined, + location: 'noData', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('should use adUnit level data if not setConfig or fetch has occured', function () { @@ -348,18 +349,66 @@ describe('the price floors module', function () { modelVersion: 'adUnit Model Version', location: 'adUnit', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('bidRequests should have getFloor function and flooring meta data when setConfig occurs', function () { - handleSetFloorsConfig({...basicFloorConfig}); + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider'}); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'floorprovider' + }); + }); + it('should pick the right floorProvider', function () { + let inputFloors = { + ...basicFloorConfig, + floorProvider: 'providerA', + data: { + ...basicFloorData, + floorProvider: 'providerB', + } + }; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'providerB' + }); + + // if not at data level take top level + delete inputFloors.data.floorProvider; + handleSetFloorsConfig(inputFloors); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + modelVersion: 'basic model', + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: 'providerA' + }); + + // if none should be undefined + delete inputFloors.floorProvider; + handleSetFloorsConfig(inputFloors); runStandardAuction(); validateBidRequests(true, { skipped: false, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('should take the right skipRate depending on input', function () { @@ -380,7 +429,8 @@ describe('the price floors module', function () { modelVersion: 'basic model', location: 'setConfig', skipRate: 50, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); // if that does not exist uses topLevel skipRate setting @@ -392,7 +442,8 @@ describe('the price floors module', function () { modelVersion: 'basic model', location: 'setConfig', skipRate: 10, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); // if that is not there defaults to zero @@ -404,12 +455,14 @@ describe('the price floors module', function () { modelVersion: 'basic model', location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('should randomly pick a model if floorsSchemaVersion is 2', function () { let inputFloors = { ...basicFloorConfig, + floorProvider: 'floorprovider', data: { floorsSchemaVersion: 2, currency: 'USD', @@ -465,7 +518,8 @@ describe('the price floors module', function () { modelVersion: 'model-1', location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: 'floorprovider' }); // 11 - 50 should use second model @@ -476,7 +530,8 @@ describe('the price floors module', function () { modelVersion: 'model-2', location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: 'floorprovider' }); // 51 - 100 should use third model @@ -487,7 +542,8 @@ describe('the price floors module', function () { modelVersion: 'model-3', location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: 'floorprovider' }); }); it('should not overwrite previous data object if the new one is bad', function () { @@ -514,7 +570,8 @@ describe('the price floors module', function () { modelVersion: 'basic model', location: 'setConfig', skipRate: 0, - fetchStatus: undefined + fetchStatus: undefined, + floorProvider: undefined }); }); it('should dynamically add new schema fileds and functions if added via setConfig', function () { @@ -591,7 +648,8 @@ describe('the price floors module', function () { modelVersion: 'basic model', location: 'setConfig', skipRate: 0, - fetchStatus: 'timeout' + fetchStatus: 'timeout', + floorProvider: undefined }); fakeFloorProvider.respond(); }); @@ -604,7 +662,7 @@ describe('the price floors module', function () { fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); // run setConfig indicating fetch - handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); // floor provider should be called expect(fakeFloorProvider.requests.length).to.equal(1); @@ -627,7 +685,44 @@ describe('the price floors module', function () { modelVersion: 'fetch model name', location: 'fetch', skipRate: 0, - fetchStatus: 'success' + fetchStatus: 'success', + floorProvider: 'floorprovider' + }); + }); + it('it should correctly overwrite floorProvider with fetch provider', function () { + // init the fake server with response stuff + let fetchFloorData = { + ...basicFloorData, + floorProvider: 'floorProviderD', // change the floor provider + modelVersion: 'fetch model name', // change the model name + }; + fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); + + // run setConfig indicating fetch + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorproviderC', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + + // floor provider should be called + expect(fakeFloorProvider.requests.length).to.equal(1); + expect(fakeFloorProvider.requests[0].url).to.equal('http://www.fakeFloorProvider.json'); + + // start the auction it should delay and not immediately call `continueAuction` + runStandardAuction(); + + // exposedAdUnits should be undefined if the auction has not continued + expect(exposedAdUnits).to.be.undefined; + + // make the fetch respond + fakeFloorProvider.respond(); + + // the exposedAdUnits should be from the fetch not setConfig level data + // and fetchStatus is success since fetch worked + validateBidRequests(true, { + skipped: false, + modelVersion: 'fetch model name', + location: 'fetch', + skipRate: 0, + fetchStatus: 'success', + floorProvider: 'floorProviderD' }); }); it('it should correctly overwrite skipRate with fetch skipRate', function () { @@ -642,7 +737,7 @@ describe('the price floors module', function () { fakeFloorProvider.respondWith(JSON.stringify(fetchFloorData)); // run setConfig indicating fetch - handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}}); // floor provider should be called expect(fakeFloorProvider.requests.length).to.equal(1); @@ -665,7 +760,8 @@ describe('the price floors module', function () { modelVersion: 'fetch model name', location: 'fetch', skipRate: 95, - fetchStatus: 'success' + fetchStatus: 'success', + floorProvider: 'floorprovider' }); }); it('Should not break if floor provider returns 404', function () { @@ -685,7 +781,8 @@ describe('the price floors module', function () { modelVersion: 'basic model', location: 'setConfig', skipRate: 0, - fetchStatus: 'error' + fetchStatus: 'error', + floorProvider: undefined }); }); it('Should not break if floor provider returns non json', function () { @@ -707,7 +804,8 @@ describe('the price floors module', function () { modelVersion: 'basic model', location: 'setConfig', skipRate: 0, - fetchStatus: 'success' + fetchStatus: 'success', + floorProvider: undefined }); }); it('should handle not using fetch correctly', function () { diff --git a/test/spec/modules/projectLimeLightBidAdapter_spec.js b/test/spec/modules/projectLimeLightBidAdapter_spec.js index 3ffc017f177..778d8eedf7b 100644 --- a/test/spec/modules/projectLimeLightBidAdapter_spec.js +++ b/test/spec/modules/projectLimeLightBidAdapter_spec.js @@ -2,11 +2,12 @@ import {expect} from 'chai'; import {spec} from '../../../modules/projectLimeLightBidAdapter.js'; describe('ProjectLimeLightAdapter', function () { - let bid = { + const bid1 = { bidId: '2dd581a2b6281d', bidder: 'project-limelight', bidderRequestId: '145e1d6a7837c9', params: { + host: 'ads.project-limelight.com', adUnitId: 123, adUnitType: 'banner' }, @@ -14,46 +15,83 @@ describe('ProjectLimeLightAdapter', function () { auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', sizes: [[300, 250]], transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' - }; + } + const bid2 = { + bidId: '58ee9870c3164a', + bidder: 'project-limelight', + bidderRequestId: '209fdaf1c81649', + params: { + host: 'cpm.project-limelight.com', + adUnitId: 456, + adUnitType: 'banner' + }, + placementCode: 'placement_1', + auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', + sizes: [[350, 200]], + transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4' + } + const bid3 = { + bidId: '019645c7d69460', + bidder: 'project-limelight', + bidderRequestId: 'f2b15f89e77ba6', + params: { + host: 'ads.project-limelight.com', + adUnitId: 789, + adUnitType: 'video' + }, + placementCode: 'placement_2', + auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', + sizes: [[800, 600]], + transactionId: '738d5915-6651-43b9-9b6b-d50517350917' + } describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; - }); - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); - }); + const serverRequests = spec.buildRequests([bid1, bid2, bid3]) + it('Creates two ServerRequests', function() { + expect(serverRequests).to.exist + expect(serverRequests).to.have.lengthOf(2) + }) + serverRequests.forEach(serverRequest => { + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist + expect(serverRequest.method).to.exist + expect(serverRequest.url).to.exist + expect(serverRequest.data).to.exist + }) + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST') + }) + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data + expect(data).to.be.an('object') + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'adUnits') + expect(data.deviceWidth).to.be.a('number') + expect(data.deviceHeight).to.be.a('number') + expect(data.secure).to.be.a('boolean') + data.adUnits.forEach(adUnit => { + expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId') + expect(adUnit.id).to.be.a('number') + expect(adUnit.bidId).to.be.a('string') + expect(adUnit.type).to.be.a('string') + expect(adUnit.transactionId).to.be.a('string') + expect(adUnit.sizes).to.be.an('array') + }) + }) + }) it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://ads.project-limelight.com/hb'); - }); - it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'adUnits'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.secure).to.be.a('boolean'); - let adUnits = data['adUnits']; - for (let i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId'); - expect(adUnit.id).to.be.a('number'); - expect(adUnit.bidId).to.be.a('string'); - expect(adUnit.type).to.be.a('string'); - expect(adUnit.transactionId).to.be.a('string'); - expect(adUnit.sizes).to.be.an('array'); - } - }); + expect(serverRequests[0].url).to.equal('https://ads.project-limelight.com/hb') + expect(serverRequests[1].url).to.equal('https://cpm.project-limelight.com/hb') + }) + it('Returns valid adUnits', function () { + validateAdUnit(serverRequests[0].data.adUnits[0], bid1) + validateAdUnit(serverRequests[1].data.adUnits[0], bid2) + validateAdUnit(serverRequests[0].data.adUnits[1], bid3) + }) it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.adUnits).to.be.an('array').that.is.empty; - }); - }); + const serverRequests = spec.buildRequests([]) + expect(serverRequests).to.be.an('array').that.is.empty + }) + }) describe('interpretBannerResponse', function () { let resObject = { body: [ { @@ -167,4 +205,55 @@ describe('ProjectLimeLightAdapter', function () { expect(spec.isBidRequestValid(bidFailed)).to.equal(false); }); }); + describe('interpretResponse', function() { + let resObject = { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + }; + it('should skip responses which do not contain required params', function() { + let bidResponses = { + body: [ { + mediaType: 'banner', + cpm: 0.3, + ttl: 1000, + currency: 'USD' + }, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); + }); + it('should skip responses which do not contain expected mediaType', function() { + let bidResponses = { + body: [ { + requestId: '123', + mediaType: 'native', + cpm: 0.3, + creativeId: '123asd', + ttl: 1000, + currency: 'USD' + }, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); + }); + }); }); + +function validateAdUnit(adUnit, bid) { + expect(adUnit.id).to.equal(bid.params.adUnitId) + expect(adUnit.bidId).to.equal(bid.bidId) + expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()) + expect(adUnit.transactionId).to.equal(bid.transactionId) + expect(adUnit.sizes).to.deep.equal(bid.sizes.map(size => { + return { + width: size[0], + height: size[1] + } + })) +} diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index ab0ef2adc51..a46ff26c4b8 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -234,7 +234,7 @@ describe('Publisher Common ID', function () { }); }); - it('disable auto create', function() { + it.skip('disable auto create', function() { setConfig({ create: false }); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index c510be99402..52f2e3aeefe 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -200,6 +200,14 @@ describe('pubGENIUS adapter', () => { expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); }); + it('should use canonical URL over referer in refererInfo', () => { + bidderRequest.refererInfo.canonicalUrl = 'http://pageurl.org'; + bidderRequest.refererInfo.referer = 'http://referer.org'; + expectedRequest.data.site = { page: 'http://pageurl.org' }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); + it('should take gdprConsent when GDPR does not apply', () => { bidderRequest.gdprConsent = { gdprApplies: false, @@ -248,7 +256,7 @@ describe('pubGENIUS adapter', () => { } ] }; - bidderRequest.schain = deepClone(schain); + bidRequest.schain = deepClone(schain); expectedRequest.data.source = { ext: { schain: deepClone(schain) }, }; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index abc440ac8ea..0f51a8df61c 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -26,6 +26,9 @@ describe('PubMatic adapter', function () { let bannerBidResponse; let videoBidResponse; let schainConfig; + let outstreamBidRequest; + let validOutstreamBidRequest; + let outstreamVideoBidResponse; beforeEach(function () { schainConfig = { @@ -55,7 +58,7 @@ describe('PubMatic adapter', function () { } }, params: { - publisherId: '301', + publisherId: '5670', adSlot: '/15671365/DMDemo@300x250:0', kadfloor: '1.2', pmzoneid: 'aabc, ddef', @@ -656,7 +659,89 @@ describe('PubMatic adapter', function () { }] }] } - } + }; + outstreamBidRequest = + [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bidder: 'pubmatic', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + publisherId: '5670', + outstreamAU: 'pubmatic-test', + adSlot: 'Div1@0x0', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + } + ]; + + validOutstreamBidRequest = { + auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', + auctionStart: 1585918458868, + bidderCode: 'pubmatic', + bidderRequestId: '47acc48ad47af5', + bids: [{ + adUnitCode: 'video1', + auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', + bidId: '47acc48ad47af5', + bidRequestsCount: 1, + bidder: 'pubmatic', + bidderRequestId: '47acc48ad47af5', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: { + publisherId: '5670', + outstreamAU: 'pubmatic-test', + adSlot: 'Div1@0x0', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + }, + sizes: [[768, 432], [640, 480], [630, 360]], + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }], + start: 11585918458869, + timeout: 3000 + }; + + outstreamVideoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '0fb4905b-1234-4152-86be-c6f6d259ba99', + 'impid': '47acc48ad47af5', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.pubmatic.com', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; }); describe('implementation', function () { @@ -725,7 +810,17 @@ describe('PubMatic adapter', function () { let request = spec.buildRequests(bidRequests); expect(request.url).to.equal('https://hbopenbid.pubmatic.com/translator?source=prebid-client'); expect(request.method).to.equal('POST'); - }); + }); + + it('should return bidderRequest property', function() { + let request = spec.buildRequests(bidRequests, validOutstreamBidRequest); + expect(request.bidderRequest).to.equal(validOutstreamBidRequest); + }); + + it('bidderRequest should be undefined if bidderRequest is not present', function() { + let request = spec.buildRequests(bidRequests); + expect(request.bidderRequest).to.be.undefined; + }); it('test flag not sent when pubmaticTest=true is absent in page url', function() { let request = spec.buildRequests(bidRequests); @@ -776,6 +871,23 @@ describe('PubMatic adapter', function () { expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); }); + it('Set content from config, set site.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Merge the device info from config', function() { let sandbox = sinon.sandbox.create(); sandbox.stub(config, 'getConfig').callsFake((key) => { @@ -840,6 +952,62 @@ describe('PubMatic adapter', function () { sandbox.restore(); }); + it('Set app, content from config, copy publisher and ext from site, unset site, config.content in app.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content, + app: { + bundle: 'org.prebid.mobile.demoapp', + domain: 'prebid.org' + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); + expect(data.app.domain).to.equal('prebid.org'); + expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); + expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); + expect(data.app.content).to.deep.equal(content); + expect(data.site).to.not.exist; + sandbox.restore(); + }); + + it('Set app.content, content from config, copy publisher and ext from site, unset site, config.app.content in app.content', function() { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + const appContent = { + id: 'app-content-id-2' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content, + app: { + bundle: 'org.prebid.mobile.demoapp', + domain: 'prebid.org', + content: appContent + } + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); + expect(data.app.domain).to.equal('prebid.org'); + expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); + expect(data.app.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); + expect(data.app.content).to.deep.equal(appContent); + expect(data.site).to.not.exist; + sandbox.restore(); + }); + it('Request params check: without adSlot', function () { delete bidRequests[0].params.adSlot; let request = spec.buildRequests(bidRequests); @@ -1517,7 +1685,7 @@ describe('PubMatic adapter', function () { describe('Parrable Id', function() { it('send the Parrable id if it is present', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.parrableid = 'parrable-user-id'; + bidRequests[0].userId.parrableId = { eid: 'parrable-user-id' }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); @@ -1530,7 +1698,7 @@ describe('PubMatic adapter', function () { }]); }); - it('do not pass if not string', function() { + it('do not pass if not object with eid key', function() { bidRequests[0].userId = {}; bidRequests[0].userId.parrableid = 1; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); @@ -2302,6 +2470,7 @@ describe('PubMatic adapter', function () { expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); expect(response[0].meta.buyerId).to.equal(976); expect(response[0].meta.clickUrl).to.equal('blackrock.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('blackrock.com'); expect(response[0].referrer).to.include(data.site.ref); expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); expect(response[0].pm_seat).to.equal(bidResponses.body.seatbid[0].seat); @@ -2325,6 +2494,7 @@ describe('PubMatic adapter', function () { expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); expect(response[1].meta.buyerId).to.equal(832); expect(response[1].meta.clickUrl).to.equal('hivehome.com'); + expect(response[1].meta.advertiserDomains[0]).to.equal('hivehome.com'); expect(response[1].referrer).to.include(data.site.ref); expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); expect(response[1].pm_seat).to.equal(bidResponses.body.seatbid[1].seat || null); @@ -2379,7 +2549,6 @@ describe('PubMatic adapter', function () { it('should check for valid video mediaType in case of multiformat request', function() { let request = spec.buildRequests(videoBidRequests); let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].mediaType).to.equal('video'); }); @@ -2389,6 +2558,36 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('native'); }); + + it('should assign renderer if bid is video and request is for outstream', function() { + let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.exist; + }); + + it('should not assign renderer if bidderRequest is not present', function() { + let request = spec.buildRequests(outstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is native', function() { + let request = spec.buildRequests(nativeBidRequests); + let response = spec.interpretResponse(nativeBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is of banner', function() { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response[0].renderer).to.not.exist; + }); }); describe('getUserSyncs', function() { diff --git a/test/spec/modules/pubperfAnalyticsAdapter_spec.js b/test/spec/modules/pubperfAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b316b44617a --- /dev/null +++ b/test/spec/modules/pubperfAnalyticsAdapter_spec.js @@ -0,0 +1,55 @@ +import pubperfAnalytics from 'modules/pubperfAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let events = require('src/events'); +let utils = require('src/utils.js'); +let constants = require('src/constants.json'); + +describe('Pubperf Analytics Adapter', function() { + describe('Prebid Manager Analytic tests', function() { + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + sinon.stub(utils, 'logError'); + }); + + afterEach(function() { + events.getEvents.restore(); + utils.logError.restore(); + }); + + it('should throw error, when pubperf_pbjs is not defined', function() { + pubperfAnalytics.enableAnalytics({ + provider: 'pubperf' + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + expect(server.requests.length).to.equal(0); + expect(utils.logError.called).to.equal(true); + }); + + it('track event without errors', function() { + sinon.spy(pubperfAnalytics, 'track'); + + window['pubperf_pbjs'] = function() {}; + + pubperfAnalytics.enableAnalytics({ + provider: 'pubperf' + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + sinon.assert.callCount(pubperfAnalytics.track, 6); + }); + }); +}); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 4b21856b68e..d71bd018ab3 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -631,7 +631,7 @@ describe('PulsePoint Adapter Tests', function () { criteoId: 'criteo_id234', idl_env: 'idl_id123', id5id: 'id5id_234', - parrableid: 'parrable_id234', + parrableId: { eid: 'parrable_id234' }, lipb: { lipbid: 'liveintent_id123' } diff --git a/test/spec/modules/quantumBidAdapter_spec.js b/test/spec/modules/quantumBidAdapter_spec.js deleted file mode 100644 index c03d74ea52e..00000000000 --- a/test/spec/modules/quantumBidAdapter_spec.js +++ /dev/null @@ -1,325 +0,0 @@ -import { expect } from 'chai' -import { spec } from 'modules/quantumBidAdapter.js' -import { newBidder } from 'src/adapters/bidderFactory.js' - -const ENDPOINT = 'https://s.sspqns.com/hb' -const REQUEST = { - 'bidder': 'quantum', - 'sizes': [[300, 250]], - 'renderMode': 'banner', - 'params': { - placementId: 21546 - } -} - -const NATIVE_REQUEST = { - 'bidder': 'quantum', - 'mediaType': 'native', - 'sizes': [[0, 0]], - 'params': { - placementId: 21546 - } -} - -const serverResponse = { - 'price': 0.3, - 'debug': [ - '' - ], - 'is_fallback': false, - 'nurl': 'https://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', - 'native': { - 'link': { - 'url': 'https://s.sspqns.com/click/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4///', - 'clicktrackers': ['https://elasticad.net'] - }, - 'assets': [ - { - 'id': 1, - 'title': { - 'text': 'ad.SSP.1x1' - }, - 'required': 1 - }, - { - 'id': 2, - 'img': { - 'w': 15, - 'h': 15, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-15x15' - } - }, - { - 'id': 3, - 'data': { - 'value': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.' - }, - 'required': 1 - }, - { - 'id': 4, - 'img': { - 'w': 500, - 'h': 500, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-500x500' - } - }, - { - 'id': 6, - 'video': { - 'vasttag': 'https://elasticad.net/vast.xml' - } - }, - { - 'id': 2001, - 'data': { - 'value': 'https://elasticad.net' - } - }, - { - 'id': 2002, - 'data': { - 'value': 'vast' - } - }, - { - 'id': 2007, - 'data': { - 'value': 'click' - } - }, - { - 'id': 10, - 'data': { - 'value': 'ad.SSP.1x1 sponsor' - } - }, - { - 'id': 2003, - 'data': { - 'value': 'https://elasticad.net' - } - }, - { - 'id': 2004, - 'data': { - 'value': 'prism' - } - }, - { - 'id': 2005, - 'data': { - 'value': '/home' - } - }, - { - 'id': 2006, - 'data': { - 'value': 'https://elasticad.net/vast.xml' - } - }, - { - 'id': 2022, - 'data': { - 'value': 'Lorem ipsum....' - } - } - ], - 'imptrackers': [], - 'ver': '1.1' - }, - 'sync': [ - 'https://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' - ] -} - -const nativeServerResponse = { - 'price': 0.3, - 'debug': [ - '' - ], - 'is_fallback': false, - 'nurl': 'https://s.sspqns.com/imp/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4/', - 'native': { - 'link': { - 'url': 'https://s.sspqns.com/click/KpQ1WNMHV-9a3HqWL_0JnujJFGo1Hnx9RS3FT_Yy8jW-Z6t_PJYmP2otidJsxE3qcY2EozzcBjRzGM7HEQcxVnjOzq0Th1cxb6A5bSp5BizTwY5SRaxx_0PgF6--8LqaF4LMUgMmhfF5k3gOOzzK6gKdavia4_w3LJ1CRWkMEwABr8bPzeovy1y4MOZsOXv7vXjPGMKJSTgphuZR57fL4u4ZFF4XY70K_TaH5bfXHMRAzE0Q38tfpTvbdFV_u2g-FoF0gjzKjiS88VnetT-Jo3qtrMphWzr52jsg5tH3L7hbymUOm1YkuJP9xrXLoZNVgC5sTMYolKLMSu6dqhS2FXcdfaGAcHweaaAAwJq-pB7DuiVcdnZQphUymhIia_KG2AYweWp6TYEpJbJjf2BcLpm_-KGw4gLh6L3DtEvUZwXZe-JpUJ4///' - }, - 'assets': [ - { - 'id': 1, - 'title': { - 'text': 'ad.SSP.1x1' - }, - 'required': 1 - }, - { - 'id': 2, - 'img': { - 'w': 15, - 'h': 15, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-15x15' - } - }, - { - 'id': 3, - 'data': { - 'value': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.' - }, - 'required': 1 - }, - { - 'id': 4, - 'img': { - 'w': 500, - 'h': 500, - 'url': 'https://files.ssp.theadtech.com.s3.amazonaws.com/media/image/sxjermpz/scalecrop-500x500' - } - }, - { - 'id': 2007, - 'data': { - 'value': 'click' - } - }, - { - 'id': 10, - 'data': { - 'value': 'ad.SSP.1x1 sponsor' - } - }, - - { - 'id': 2003, - 'data': { - 'value': 'https://elasticad.net' - } - } - ], - 'imptrackers': [], - 'ver': '1.1' - }, - 'sync': [ - 'https://match.adsrvr.org/track/cmb/generic?ttd_pid=s6e8ued&ttd_tpi=1' - ] -} - -describe('quantumBidAdapter', function () { - const adapter = newBidder(spec) - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) - }) - - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(REQUEST)).to.equal(true) - }) - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, REQUEST) - delete bid.params - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - }) - - describe('buildRequests', function () { - let bidRequests = [REQUEST] - - const request = spec.buildRequests(bidRequests, {}) - - it('sends bid request to ENDPOINT via GET', function () { - expect(request[0].method).to.equal('GET') - }) - }) - - describe('GDPR conformity', function () { - const bidRequests = [{ - 'bidder': 'quantum', - 'mediaType': 'native', - 'params': { - placementId: 21546 - }, - adUnitCode: 'aaa', - transactionId: '2b8389fe-615c-482d-9f1a-376fb8f7d6b0', - sizes: [[0, 0]], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: { - consentString: 'awefasdfwefasdfasd', - gdprApplies: true - } - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.quantx_gdpr).to.equal(1); - expect(requests[0].data.quantx_user_consent_string).to.equal('awefasdfwefasdfasd'); - }); - }); - - describe('GDPR absence conformity', function () { - const bidRequests = [{ - 'bidder': 'quantum', - 'mediaType': 'native', - 'params': { - placementId: 21546 - }, - adUnitCode: 'aaa', - transactionId: '2b8389fe-615c-482d-9f1a-376fb8f7d6b0', - sizes: [[0, 0]], - bidId: '1abgs362e0x48a8', - bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' - }]; - - const bidderRequest = { - gdprConsent: undefined - }; - - it('should transmit correct data', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].data.quantx_gdpr).to.be.undefined; - expect(requests[0].data.quantx_user_consent_string).to.be.undefined; - }); - }); - - describe('interpretResponse', function () { - let bidderRequest = { - bidderCode: 'bidderCode', - bids: [] - } - - it('handles native request : should get correct bid response', function () { - const result = spec.interpretResponse({body: nativeServerResponse}, NATIVE_REQUEST) - expect(result[0]).to.have.property('cpm').equal(0.3) - expect(result[0]).to.have.property('width').to.be.below(2) - expect(result[0]).to.have.property('height').to.be.below(2) - expect(result[0]).to.have.property('mediaType').equal('native') - expect(result[0]).to.have.property('native') - }) - - it('should get correct bid response', function () { - const result = spec.interpretResponse({body: serverResponse}, REQUEST) - expect(result[0]).to.have.property('cpm').equal(0.3) - expect(result[0]).to.have.property('width').equal(300) - expect(result[0]).to.have.property('height').equal(250) - expect(result[0]).to.have.property('mediaType').equal('banner') - expect(result[0]).to.have.property('ad') - }) - - it('handles nobid responses', function () { - const nobidServerResponse = {bids: []} - const nobidResult = spec.interpretResponse({body: nobidServerResponse}, bidderRequest) - // console.log(nobidResult) - expect(nobidResult.length).to.equal(0) - }) - }) -}) diff --git a/test/spec/modules/rtbsapeBidAdapter_spec.js b/test/spec/modules/rtbsapeBidAdapter_spec.js new file mode 100644 index 00000000000..4c3814385b3 --- /dev/null +++ b/test/spec/modules/rtbsapeBidAdapter_spec.js @@ -0,0 +1,166 @@ +import {expect} from 'chai'; +import {spec} from 'modules/rtbsapeBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {executeRenderer, Renderer} from 'src/Renderer.js'; + +describe('rtbsapeBidAdapterTests', function () { + describe('isBidRequestValid', function () { + it('valid', function () { + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {placeId: 4321}})).to.equal(true); + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {video: true}, params: {placeId: 4321}})).to.equal(true); + }); + + it('invalid', function () { + expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {}})).to.equal(false); + expect(spec.isBidRequestValid({bidder: 'rtbsape', params: {placeId: 4321}})).to.equal(false); + }); + }); + + it('buildRequests', function () { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'rtbsape', + params: {placeId: 4321}, + sizes: [[240, 400]] + }]; + let bidderRequest = { + auctionId: '2e208334-cafe-4c2c-b06b-f055ff876852', + bidderRequestId: '1392d0aa613366', + refererInfo: {} + }; + let request = spec.buildRequests(bidRequestData, bidderRequest); + expect(request.data.auctionId).to.equal('2e208334-cafe-4c2c-b06b-f055ff876852'); + expect(request.data.requestId).to.equal('1392d0aa613366'); + expect(request.data.bids[0].bidId).to.equal('bid1234'); + expect(request.data.timezone).to.not.equal(undefined); + }); + + describe('interpretResponse', function () { + it('banner', function () { + let serverResponse = { + body: { + bids: [{ + requestId: 'bid1234', + cpm: 2.21, + currency: 'RUB', + width: 240, + height: 400, + netRevenue: true, + ad: 'Ad html' + }] + } + }; + let bids = spec.interpretResponse(serverResponse, {data: {bids: [{mediaTypes: {banner: true}}]}}); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.cpm).to.equal(2.21); + expect(bid.currency).to.equal('RUB'); + expect(bid.width).to.equal(240); + expect(bid.height).to.equal(400); + expect(bid.netRevenue).to.equal(true); + expect(bid.requestId).to.equal('bid1234'); + expect(bid.ad).to.equal('Ad html'); + }); + + describe('video (outstream)', function () { + let bid; + + before(() => { + let serverResponse = { + body: { + bids: [{ + requestId: 'bid1234', + adUnitCode: 'ad-bid1234', + cpm: 3.32, + currency: 'RUB', + width: 600, + height: 340, + netRevenue: true, + vastUrl: 'https://cdn-rtb.sape.ru/vast/4321.xml', + meta: { + mediaType: 'video' + } + }] + } + }; + let serverRequest = { + data: { + bids: [{ + bidId: 'bid1234', + adUnitCode: 'ad-bid1234', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: { + placeId: 4321, + video: { + playerMuted: false + } + } + }] + } + }; + let bids = spec.interpretResponse(serverResponse, serverRequest); + expect(bids).to.have.lengthOf(1); + bid = bids[0]; + }); + + it('should add renderer', () => { + expect(bid).to.have.own.property('renderer'); + expect(bid.renderer).to.be.instanceof(Renderer); + expect(bid.renderer.url).to.equal('https://cdn-rtb.sape.ru/js/player.js'); + expect(bid.playerMuted).to.equal(false); + }); + + it('should create player instance', () => { + let spy = false; + + window.sapeRtbPlayerHandler = function (id, w, h, m) { + const player = {addSlot: () => [id, w, h, m]} + expect(spy).to.equal(false); + spy = sinon.spy(player, 'addSlot'); + return player; + }; + + executeRenderer(bid.renderer, bid); + expect(spy).to.not.equal(false); + expect(spy.called).to.be.true; + + const spyCall = spy.getCall(0); + expect(spyCall.args[0].url).to.be.equal('https://cdn-rtb.sape.ru/vast/4321.xml'); + expect(spyCall.returnValue[0]).to.be.equal('ad-bid1234'); + expect(spyCall.returnValue[1]).to.be.equal(600); + expect(spyCall.returnValue[2]).to.be.equal(340); + expect(spyCall.returnValue[3]).to.be.equal(false); + }); + }); + }); + + it('getUserSyncs', function () { + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.be.an('array').that.to.have.lengthOf(1); + expect(syncs[0]).to.deep.equal({type: 'iframe', url: 'https://www.acint.net/mc/?dp=141'}); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('called once', function () { + spec.onBidWon({cpm: '2.21', nurl: 'https://ssp-rtb.sape.ru/track?event=win'}); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + + it('called false', function () { + spec.onBidWon({cpm: '2.21'}); + expect(utils.triggerPixel.called).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 1639389fe24..0c2c83a4b37 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -659,14 +659,15 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(ANALYTICS_MESSAGE); }); - it('should capture price floor information correctly', function () { + function performFloorAuction(provider) { let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); auctionInit.bidderRequests[0].bids[0].floorData = { skipped: false, modelVersion: 'someModelName', location: 'setConfig', skipRate: 15, - fetchStatus: 'error' + fetchStatus: 'error', + floorProvider: provider }; let flooredResponse = { ...BID, @@ -727,6 +728,46 @@ describe('rubicon analytics adapter', function () { let message = JSON.parse(server.requests[0].requestBody); validate(message); + return message; + } + + it('should capture price floor information correctly', function () { + let message = performFloorAuction('rubicon') + + // verify our floor stuff is passed + // top level floor info + expect(message.auctions[0].floors).to.deep.equal({ + location: 'setConfig', + modelName: 'someModelName', + skipped: false, + enforcement: true, + dealsEnforced: false, + skipRate: 15, + fetchStatus: 'error', + provider: 'rubicon' + }); + // first adUnit's adSlot + expect(message.auctions[0].adUnits[0].adSlot).to.equal('12345/sports'); + // since no other bids, we set adUnit status to no-bid + expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); + // first adUnits bid is rejected + expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected'); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); + // if bid rejected should take cpmAfterAdjustments val + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[1].adSlot).to.equal('12345/news'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[1].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); + }); + + it('should still send floor info if provider is not rubicon', function () { + let message = performFloorAuction('randomProvider') // verify our floor stuff is passed // top level floor info @@ -737,7 +778,8 @@ describe('rubicon analytics adapter', function () { enforcement: true, dealsEnforced: false, skipRate: 15, - fetchStatus: 'error' + fetchStatus: 'error', + provider: 'randomProvider' }); // first adUnit's adSlot expect(message.auctions[0].adUnits[0].adSlot).to.equal('12345/sports'); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 11ed17df5f8..c21a9b879f1 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1364,25 +1364,25 @@ describe('the rubicon adapter', function () { } }); - it('should not send \"tg_i.dfp_ad_unit_code\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.pbadslot’\" if \"fpd.context\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.pbAdSlot\" is undefined', function () { + it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" is undefined', function () { bidderRequest.bids[0].fpd = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { + it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { bidderRequest.bids[0].fpd = { context: { pbAdSlot: '' @@ -1393,10 +1393,10 @@ describe('the rubicon adapter', function () { const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data).to.not.have.property('tg_i.pbadslot'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { + it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { bidderRequest.bids[0].fpd = { context: { pbAdSlot: 'abc' @@ -1406,15 +1406,92 @@ describe('the rubicon adapter', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.pbadslot'); + expect(data['tg_i.pbadslot']).to.equal('abc'); + }); + + it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].fpd = { + context: { + pbAdSlot: '/a/b/c' + } + }; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('tg_i.pbadslot'); + expect(data['tg_i.pbadslot']).to.equal('a/b/c'); + }); + }); + + describe('GAM ad unit', function () { + beforeEach(function () { + // enforce that the bid at 0 does not have a 'context' property + if (bidderRequest.bids[0].hasOwnProperty('fpd')) { + delete bidderRequest.bids[0].fpd; + } + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context\" object is not valid', function () { + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" is undefined', function () { + bidderRequest.bids[0].fpd = {}; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + }); + + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" value is an empty string', function () { + bidderRequest.bids[0].fpd = { + context: { + adServer: { + adSlot: '' + } + } + }; + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + }); + + it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string', function () { + bidderRequest.bids[0].fpd = { + context: { + adServer: { + adSlot: 'abc' + } + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + expect(data).to.be.an('Object'); expect(data).to.have.property('tg_i.dfp_ad_unit_code'); expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { + it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string, but all leading slash characters should be removed', function () { bidderRequest.bids[0].fpd = { context: { - pbAdSlot: '/a/b/c' + adServer: { + adSlot: 'a/b/c' + } } }; @@ -1839,6 +1916,24 @@ describe('the rubicon adapter', function () { bidderRequest.auctionStart + 100 ); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + }); + + it('should include GAM ad unit in bid request', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].fpd = { + context: { + adServer: { + adSlot: '1234567890' + } + } + }; + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(request.data.imp[0].ext.context.data.adslot).to.equal('1234567890'); }); @@ -1856,22 +1951,6 @@ describe('the rubicon adapter', function () { }); }); - it('should include pbAdSlot in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' - } - }; - - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adslot).to.equal('1234567890'); - }); - describe('combineSlotUrlParams', function () { it('should combine an array of slot url params', function () { expect(spec.combineSlotUrlParams([])).to.deep.equal({}); diff --git a/test/spec/modules/saambaaBidAdapter_spec.js b/test/spec/modules/saambaaBidAdapter_spec.js new file mode 100755 index 00000000000..80a85ae895d --- /dev/null +++ b/test/spec/modules/saambaaBidAdapter_spec.js @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { spec } from 'modules/saambaaBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; + +describe('saambaaBidAdapter', function () { + let bidRequests; + let bidRequestsVid; + + beforeEach(function () { + bidRequests = [{'bidder': 'saambaa', 'params': {'pubid': '121ab139faf7ac67428a23f1d0a9a71b', 'floor': 0.5, 'placement': 1234, size: '320x250'}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + + bidRequestsVid = [{'bidder': 'saambaa', 'params': {'pubid': '121ab139faf7ac67428a23f1d0a9a71b', 'floor': 1.0, 'placement': 1234, size: '320x480', 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + }); + + describe('spec.isBidRequestValid', function () { + it('should return true when the required params are passed for banner', function () { + const bidRequest = bidRequests[0]; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the required params are passed for video', function () { + const bidRequests = bidRequestsVid[0]; + expect(spec.isBidRequestValid(bidRequests)).to.equal(true); + }); + + it('should return false when no pub id params are passed', function () { + const bidRequest = bidRequests[0]; + bidRequest.params.pubid = ''; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no placement params are passed', function () { + const bidRequest = bidRequests[0]; + bidRequest.params.placement = ''; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', function () { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', function () { + it('should create a POST request for each bid', function () { + const bidRequest = bidRequests[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + }); + + it('should create a POST request for each bid in video request', function () { + const bidRequest = bidRequestsVid[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + }); + + it('should have domain in request', function () { + const bidRequest = bidRequests[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].data.site.domain).length !== 0; + }); + }); + + describe('spec.interpretResponse', function () { + describe('for banner bids', function () { + it('should return no bids if the response is not valid', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + + if (typeof bidResponse !== 'undefined') { + expect(bidResponse.length).to.equal(0); + } else { + expect(true).to.equal(true); + } + }); + + it('should return no bids if the response is empty', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); + if (typeof bidResponse !== 'undefined') { + expect(bidResponse.length).to.equal(0); + } else { expect(true).to.equal(true); } + }); + + it('should return valid video bid responses', function () { + let _mediaTypes = VIDEO; + const saambaabidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; + const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} + const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, saambaabidreqVid); + delete bidResponseVid['vastUrl']; + delete bidResponseVid['ad']; + expect(bidResponseVid).to.deep.equal({ + requestId: bidRequestsVid[0].bidId, + bidderCode: 'saambaa', + creativeId: serverResponseVid.seatbid[0].bid[0].crid, + cpm: serverResponseVid.seatbid[0].bid[0].price, + width: serverResponseVid.seatbid[0].bid[0].w, + height: serverResponseVid.seatbid[0].bid[0].h, + mediaType: 'video', + meta: { advertiserDomains: serverResponseVid.seatbid[0].bid[0].adomain }, + currency: 'USD', + netRevenue: true, + ttl: 60 + }); + }); + + it('should return valid banner bid responses', function () { + const saambaabidreq = {bids: {}}; + bidRequests.forEach(bid => { + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); + saambaabidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], + h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] + + }; + }); + const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
', 'adomain': ['advertiserdomain.com'], 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; + + const bidResponse = spec.interpretResponse({ body: serverResponse }, saambaabidreq); + expect(bidResponse).to.deep.equal({ + requestId: bidRequests[0].bidId, + ad: serverResponse.seatbid[0].bid[0].adm, + bidderCode: 'saambaa', + creativeId: serverResponse.seatbid[0].bid[0].crid, + cpm: serverResponse.seatbid[0].bid[0].price, + width: serverResponse.seatbid[0].bid[0].w, + height: serverResponse.seatbid[0].bid[0].h, + mediaType: 'banner', + meta: { advertiserDomains: serverResponse.seatbid[0].bid[0].adomain }, + currency: 'USD', + netRevenue: true, + ttl: 60 + }); + }); + }); + }); +}); diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 2462db3b144..77e17ef360f 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -235,7 +235,7 @@ describe('sizeMappingV2', function () { adUnitSetupChecks.validateBannerMediaType.restore(); }); - it('should delete banner mediaType if it does not constain sizes or sizeConfig property', function () { + it('should delete banner mediaType if it does not contain sizes or sizeConfig property', function () { let adUnits = utils.deepClone(AD_UNITS); delete adUnits[0].mediaTypes.banner.sizeConfig; @@ -255,7 +255,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, 'Detected a mediaTypes.banner object did not include required property sizes or sizeConfig. Removing invalid mediaTypes.banner object from Ad Unit.'); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: 'mediaTypes.banner' does not contain either 'sizes' or 'sizeConfig' property. Removing 'mediaTypes.banner' from ad unit.`); }); it('should call function "validateBannerMediaType" if mediaTypes.sizes is present', function () { @@ -293,7 +293,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig is NOT an Array. Removing the invalid object mediaTypes.banner from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizeConfig' in 'mediaTypes.banner.sizeConfig'. Removing mediaTypes.banner from ad unit.`); }); it('should delete mediaTypes.banner object if it\'s property sizeConfig does not contain the required properties "minViewPort" and "sizes"', function () { @@ -329,7 +329,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[2] is missing required property minViewPort or sizes or both.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.banner.sizeConfig[2]'. Removing mediaTypes.banner from ad unit.`); }); it('should delete mediaTypes.banner object if it\'s property sizeConfig has declared minViewPort property which is NOT an Array of two integers', function () { @@ -365,7 +365,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[0] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'minViewPort' in 'mediaTypes.banner.sizeConfig[0]'. Removing mediaTypes.banner from ad unit.`); }); it('should delete mediaTypes.banner object if it\'s property sizeConfig has declared sizes property which is not in the format, [[vw1, vh1], [vw2, vh2]], where vw is viewport width and vh is viewport height', function () { @@ -401,7 +401,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[1] has propery sizes declared with invalid value. Please ensure the sizes are listed like: [[300, 250], ...] or like: [] if no sizes are present for that size bucket.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizes' in 'mediaTypes.banner.sizeConfig[1]'. Removing mediaTypes.banner from ad unit.`); }); it('should convert sizeConfig.sizes to an array of array, i.e., [360, 600] to [[360, 600]]', function () { @@ -427,6 +427,21 @@ describe('sizeMappingV2', function () { expect(validatedAdUnits[0].mediaTypes.banner.sizeConfig[0].sizes).to.deep.equal([[]]); }); + it('should log an error message if "sizes" in sizeConfig is not declared as an array', function () { + const adUnits = utils.deepClone(AD_UNITS); + const badSizeConfig = [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [750, 0], sizes: { 'incorrect': 'format' } }, + { minViewPort: [1200, 0], sizes: [[300, 250], [300, 600]] } + ] + adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig; + + checkAdUnitSetupHook(adUnits); + + // Assertions + sinon.assert.callCount(utils.logError, 1); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizes' in 'mediaTypes.banner.sizeConfig[1]'. Removing mediaTypes.banner from ad unit.`); + }); it('should NOT delete mediaTypes.banner object if sizeConfig object is declared correctly', function () { const adUnits = utils.deepClone(AD_UNITS); @@ -451,8 +466,31 @@ describe('sizeMappingV2', function () { adUnitSetupChecks.validateVideoMediaType.restore(); }); - it('should call function "validateVideoMediaType" if mediaTypes.video.playerSize is present in the Ad Unit', function () { + it('should call function "validateVideoMediaType" if mediaTypes.video.playerSize is present in the Ad Unit (PART - 1)', function () { + // PART - 1 (Ad unit has banner.sizes defined, so, validateVideoMediaType function would be called with 'validatedBanner' as an argument) + + const adUnits = utils.deepClone(AD_UNITS); + + checkAdUnitSetupHook(adUnits); + + // since adUntis[1].mediaTypes.video has defined property "playserSize", it should call function "validateVideoMediaType" only once + sinon.assert.callCount(adUnitSetupChecks.validateVideoMediaType, 1); + /* + 'validateVideoMediaType' function should be called with 'validatedBanner' as an argument instead of the adUnit because validatedBanner is already a processed form of adUnit and is validated by banner checks. + It is not 'undefined' in this case because the adUnit[1] is using 'mediaTypes.banner.sizes' which will populate data into 'validatedBanner' variable. + + 'validatedBanner' will be idetical to adUnits[1] with the exceptions of an added property, 'sizes' on the validateBanner object itself. + */ + const validatedBanner = adUnits[1]; + validatedBanner.sizes = [[300, 250], [300, 600]]; + sinon.assert.calledWith(adUnitSetupChecks.validateVideoMediaType, validatedBanner); + }); + + it('should call function "validateVideoMediaType" if mediaTypes.video.playerSize" is present in the Ad Unit (PART - 2)', function () { + // PART - 2 (Ad unit does not have banner.sizes defined, so, validateVideoMediaType function would be called with 'adUnit' as an argument) + const adUnits = utils.deepClone(AD_UNITS); + delete adUnits[1].mediaTypes.banner; checkAdUnitSetupHook(adUnits); @@ -480,7 +518,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig is NOT an Array. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizeConfig' in 'mediaTypes.video.sizeConfig'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should delete mediaTypes.video.sizeConfig property if sizeConfig does not contain the required properties "minViewPort" and "playerSize"', function () { @@ -503,7 +541,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[0] is missing required property minViewPort or playerSize or both. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.video.sizeConfig[0]'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should delete mediaTypes.video.sizeConfig property if sizeConfig has declared minViewPort property which is NOT an Array of two integers', function () { @@ -526,7 +564,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[1] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'minViewPort' in 'mediaTypes.video.sizeConfig[1]'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should delete mediaTypes.video.sizeConfig property if sizeConfig has declared "playerSize" property which is not in the format, [[vw1, vh1]], where vw is viewport width and vh is viewport height', function () { @@ -549,7 +587,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[0] has propery playerSize declared with invalid value. Please ensure the playerSize is listed like: [640, 480] or like: [] if no playerSize is present for that size bucket.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'playerSize' in 'mediaTypes.video.sizeConfig[0]'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should convert sizeConfig.playerSize to an array of array, i.e., [360, 600] to [[360, 600]]', function () { @@ -602,7 +640,72 @@ describe('sizeMappingV2', function () { it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined', function () { const adUnits = utils.deepClone(AD_UNITS); checkAdUnitSetupHook(adUnits); + + sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + }); + + it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined (PART - 1)', function () { + // PART - 1 (Ad unit contains 'banner', 'video' and 'native' media types) + const adUnit = [{ + code: 'ad-unit-1', + mediaTypes: { + banner: { + sizes: [[300, 400]] + }, + video: { + playerSize: [[600, 400]] + }, + native: {} + }, + bids: [] + }]; + + checkAdUnitSetupHook(adUnit); + + // 'validatedVideo' should be passed as an argument to "validatedNativeMediaType" + const validatedVideo = adUnit[0]; + validatedVideo.sizes = [[600, 400]]; + sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + sinon.assert.calledWith(adUnitSetupChecks.validateNativeMediaType, validatedVideo); + }); + + it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined (PART - 2)', function () { + // PART - 2 (Ad unit contains only 'banner' and 'native' media types) + const adUnit = [{ + code: 'ad-unit-1', + mediaTypes: { + banner: { + sizes: [[300, 400]] + }, + native: {} + }, + bids: [] + }]; + + checkAdUnitSetupHook(adUnit); + + // 'validatedBanner' should be passed as an argument to "validatedNativeMediaType" + const validatedBanner = adUnit[0]; + validatedBanner.sizes = [[300, 400]]; + sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + sinon.assert.calledWith(adUnitSetupChecks.validateNativeMediaType, validatedBanner); + }); + + it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined (PART - 3)', function () { + // PART - 2 (Ad unit contains only 'native' media types) + const adUnit = [{ + code: 'ad-unit-1', + mediaTypes: { + native: {} + }, + bids: [] + }]; + + checkAdUnitSetupHook(adUnit); + + // 'adUnit[0]' should be passed as an argument to "validatedNativeMediaType" sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + sinon.assert.calledWith(adUnitSetupChecks.validateNativeMediaType, adUnit[0]); }); it('should delete mediaTypes.native.sizeConfig property if sizeConfig does not contain the required properties "minViewPort" and "active"', function () { @@ -618,7 +721,7 @@ describe('sizeMappingV2', function () { const validatedAdUnits = checkAdUnitSetupHook(adUnits); expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig'); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig is missing required property minViewPort or active or both. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.native.sizeConfig[1]'. Removing mediaTypes.native.sizeConfig from ad unit.`); }); it('should delete mediaTypes.native.sizeConfig property if sizeConfig[].minViewPort is NOT an array of TWO integers', function () { @@ -634,7 +737,7 @@ describe('sizeMappingV2', function () { const validatedAdUnits = checkAdUnitSetupHook(adUnits); expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig'); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'minViewPort' in 'mediaTypes.native.sizeConfig[0]'. Removing mediaTypes.native.sizeConfig from ad unit.`); }); it('should delete mediaTypes.native.sizeConfig property if sizeConfig[].active is NOT a Boolean', function () { @@ -651,7 +754,7 @@ describe('sizeMappingV2', function () { const validatedAdUnits = checkAdUnitSetupHook(adUnits); expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig'); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'active' in 'mediaTypes.native.sizeConfig[0]'. Removing mediaTypes.native.sizeConfig from ad unit.`); }); it('should NOT delete mediaTypes.native.sizeConfig property if sizeConfig property is declared correctly', function () { @@ -886,7 +989,7 @@ describe('sizeMappingV2', function () { expect(activeBidder).to.equal(true); }); - it('should throw a warning message if labelAny/labelAll operator found on adunit/bidder when "label" is not passed to pbjs.requestBids', function() { + it('should throw a warning message if labelAny/labelAll operator found on adunit/bidder when "label" is not passed to pbjs.requestBids', function () { const adUnit = { code: 'ad-unit-1', mediaTypes: { @@ -1209,7 +1312,7 @@ describe('sizeMappingV2', function () { sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: div-gpt-ad-1460505748561-1(1) => Active size buckets after filtration: `, adUnitDetailFixture_2.sizeBucketToSizeMap); }); - it('should increment "instance" count if presence of "Identical ad units" is detected', function() { + it('should increment "instance" count if presence of "Identical ad units" is detected', function () { const adUnit = { code: 'div-gpt-ad-1460505748561-0', mediaTypes: { @@ -1235,7 +1338,7 @@ describe('sizeMappingV2', function () { expect(adUnitDetail.instance).to.equal(2); }); - it('should not execute "getFilteredMediaTypes" function if label is not activated on the ad unit', function() { + it('should not execute "getFilteredMediaTypes" function if label is not activated on the ad unit', function () { const [adUnit] = utils.deepClone(AD_UNITS); adUnit.labelAny = ['tablet']; getAdUnitDetail('a1b2c3', adUnit, labels); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js new file mode 100644 index 00000000000..95eb36d8a0d --- /dev/null +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -0,0 +1,505 @@ +import { spec } from 'modules/smaatoBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; + +const imageAd = { + image: { + img: { + url: 'https://prebid/static/ad.jpg', + w: 320, + h: 50, + ctaurl: 'https://prebid/track/ctaurl' + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } +}; + +const richmediaAd = { + richmedia: { + mediadata: { + content: '

RICHMEDIA CONTENT

', + w: 800, + h: 600 + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } +}; + +const ADTYPE_IMG = 'Img'; +const ADTYPE_RICHMEDIA = 'Richmedia'; +const ADTYPE_VIDEO = 'Video'; + +const context = { + keywords: 'power tools,drills' +}; + +const user = { + keywords: 'a,b', + gender: 'M', + yob: 1984 +}; + +const openRtbBidResponse = (adType) => { + let adm = ''; + + switch (adType) { + case ADTYPE_IMG: + adm = JSON.stringify(imageAd); + break; + case ADTYPE_RICHMEDIA: + adm = JSON.stringify(richmediaAd); + break; + case ADTYPE_VIDEO: + adm = ''; + break; + default: + throw Error('Invalid AdType'); + } + + let resp = { + body: { + bidid: '04db8629-179d-4bcd-acce-e54722969006', + cur: 'USD', + ext: {}, + id: '5ebea288-f13a-4754-be6d-4ade66c68877', + seatbid: [ + { + bid: [ + { + 'adm': adm, + 'adomain': [ + 'smaato.com' + ], + 'bidderName': 'smaato', + 'cid': 'CM6523', + 'crid': 'CR69381', + 'dealid': '12345', + 'id': '6906aae8-7f74-4edd-9a4f-f49379a3cadd', + 'impid': '226416e6e6bf41', + 'iurl': 'https://prebid/iurl', + 'nurl': 'https://prebid/nurl', + 'price': 0.01, + 'w': 350, + 'h': 50 + } + ], + seat: 'CM6523' + } + ], + }, + headers: { + get: function (header) { + if (header === 'X-SMT-ADTYPE') { + return adType; + } + } + } + }; + return resp; +}; + +const request = { + method: 'POST', + url: 'https://prebid.ad.smaato.net/oapi/prebid', + data: '' +}; + +const interpretedBidsImg = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '
\"\"\"\"
', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } +]; + +const interpretedBidsRichmedia = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + ad: '

RICHMEDIA CONTENT

\"\"\"\"
', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'banner' + } + } +]; + +const interpretedBidsVideo = [ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + vastXml: '', + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'video' + } + } +]; + +const defaultBidderRequest = { + gdprConsent: { + consentString: 'HFIDUYFIUYIUYWIPOI87392DSU', + gdprApplies: true + }, + uspConsent: 'uspConsentString', + refererInfo: { + referer: 'http://example.com/page.html', + }, + timeout: 1200 +}; + +const minimalBidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } +}; + +const singleBannerBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +const singleVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[768, 1024]], + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + maxduration: 30, + startdelay: 0, + linearity: 1, + protocols: [7], + skip: 1, + skipmin: 5, + api: [7], + ext: {rewarded: 0} + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +const combinedBannerAndVideoBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + }, + video: { + context: 'outstream', + playerSize: [[768, 1024]], + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + maxduration: 30, + startdelay: 0, + linearity: 1, + protocols: [7], + skip: 1, + skipmin: 5, + api: [7], + ext: {rewarded: 0} + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + +describe('smaatoBidAdapterTest', () => { + describe('isBidRequestValid', () => { + it('has valid params', () => { + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: '456'}})).to.be.true; + expect(spec.isBidRequestValid(singleBannerBidRequest)).to.be.true; + }); + it('has invalid params', () => { + expect(spec.isBidRequestValid({})).to.be.false; + expect(spec.isBidRequestValid({params: {}})).to.be.false; + expect(spec.isBidRequestValid({params: {publisherId: '123'}})).to.be.false; + expect(spec.isBidRequestValid({params: {publisherId: '123', adspaceId: 456}})).to.be.false; + }); + }); + + describe('buildRequests', () => { + beforeEach(() => { + this.req = JSON.parse(spec.buildRequests([singleBannerBidRequest], defaultBidderRequest).data); + this.sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + this.sandbox.restore(); + }); + + it('can override endpoint', () => { + const overridenEndpoint = 'https://prebid/bidder'; + let bidRequest = utils.deepClone(singleBannerBidRequest); + utils.deepSetValue(bidRequest, 'params.endpoint', overridenEndpoint); + const actualEndpoint = spec.buildRequests([bidRequest], defaultBidderRequest).url; + expect(actualEndpoint).to.equal(overridenEndpoint); + }); + + it('sends correct imps', () => { + expect(this.req.imp).to.deep.equal([ + { + id: 'bidId', + banner: { + w: 300, + h: 50, + format: [ + { + h: 50, + w: 300 + } + ] + }, + tagid: 'adspaceId' + } + ]) + }); + + it('sends correct site', () => { + expect(this.req.site.id).to.exist.and.to.be.a('string'); + expect(this.req.site.domain).to.exist.and.to.be.a('string'); + expect(this.req.site.page).to.exist.and.to.be.a('string'); + expect(this.req.site.ref).to.equal('http://example.com/page.html'); + expect(this.req.site.publisher.id).to.equal('publisherId'); + }) + + it('sends gdpr applies if exists', () => { + expect(this.req.regs.ext.gdpr).to.equal(1); + expect(this.req.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); + }); + + it('sends no gdpr applies if no gdpr exists', () => { + let req_without_gdpr = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); + expect(req_without_gdpr.regs.ext.gdpr).to.not.exist; + expect(req_without_gdpr.user.ext.consent).to.not.exist; + }); + + it('sends usp if exists', () => { + expect(this.req.regs.ext.us_privacy).to.equal('uspConsentString'); + }); + + it('sends tmax', () => { + expect(this.req.tmax).to.equal(1200); + }); + + it('sends no usp if no usp exists', () => { + let req_without_usp = JSON.parse(spec.buildRequests([singleBannerBidRequest], minimalBidderRequest).data); + expect(req_without_usp.regs.ext.us_privacy).to.not.exist; + }); + + it('sends fp data', () => { + this.sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + fpd: { + context, + user + } + }; + return utils.deepAccess(config, key); + }); + let bidRequest = utils.deepClone(singleBannerBidRequest); + let req_fpd = JSON.parse(spec.buildRequests([bidRequest], defaultBidderRequest).data); + expect(req_fpd.user.gender).to.equal('M'); + expect(req_fpd.user.yob).to.equal(1984); + expect(req_fpd.user.keywords).to.eql('a,b'); + expect(req_fpd.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); + expect(req_fpd.site.keywords).to.eql('power tools,drills'); + expect(req_fpd.site.publisher.id).to.equal('publisherId'); + }) + }); + + describe('buildRequests for video imps', () => { + it('sends correct video imps', () => { + let req = JSON.parse(spec.buildRequests([singleVideoBidRequest], defaultBidderRequest).data); + expect(req.imp).to.deep.equal([ + { + id: 'bidId', + video: { + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + startdelay: 0, + linearity: 1, + h: 1024, + maxduration: 30, + skip: 1, + protocols: [7], + ext: { + rewarded: 0 + }, + skipmin: 5, + api: [7], + w: 768 + }, + tagid: 'adspaceId' + } + ]) + }); + it('allows combines banner and video imp in single bid request', () => { + let req = JSON.parse(spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest).data); + expect(req.imp).to.deep.equal([ + { + id: 'bidId', + banner: { + w: 300, + h: 50, + format: [ + { + h: 50, + w: 300 + } + ] + }, + video: { + mimes: ['video\/mp4', 'video\/quicktime', 'video\/3gpp', 'video\/x-m4v'], + minduration: 5, + startdelay: 0, + linearity: 1, + h: 1024, + maxduration: 30, + skip: 1, + protocols: [7], + ext: { + rewarded: 0 + }, + skipmin: 5, + api: [7], + w: 768 + }, + tagid: 'adspaceId' + } + ]) + }); + it('allows two imps in the same bid request', () => { + let req = JSON.parse(spec.buildRequests([singleBannerBidRequest, singleBannerBidRequest], defaultBidderRequest).data); + expect(req.imp).to.have.length(2); + }); + }); + + describe('interpretResponse', () => { + it('single image reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_IMG), request); + assert.deepStrictEqual(bids, interpretedBidsImg); + }); + it('single richmedia reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_RICHMEDIA), request); + assert.deepStrictEqual(bids, interpretedBidsRichmedia); + }); + it('single video reponse', () => { + const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_VIDEO), request); + assert.deepStrictEqual(bids, interpretedBidsVideo); + }); + it('ignores bid response with invalid ad type', () => { + let resp = openRtbBidResponse(ADTYPE_IMG); + resp.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return undefined; + } + } + const bids = spec.interpretResponse(resp, request); + expect(bids).to.be.empty + }); + it('uses correct TTL when expire header exists', () => { + const clock = sinon.useFakeTimers(); + clock.tick(2000); + let resp = openRtbBidResponse(ADTYPE_IMG); + resp.headers.get = (header) => { + if (header === 'X-SMT-ADTYPE') { + return ADTYPE_IMG; + } + if (header === 'X-SMT-Expires') { + return 2000 + (400 * 1000); + } + } + const bids = spec.interpretResponse(resp, request); + expect(bids[0].ttl).to.equal(400); + clock.restore(); + }); + }); +}); diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 78d17f35c69..94cc335fd8e 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -381,6 +381,7 @@ describe('the spotx adapter', function () { impid: 123, cur: 'USD', price: 12, + adomain: ['abc.com'], crid: 321, w: 400, h: 300, @@ -392,6 +393,7 @@ describe('the spotx adapter', function () { impid: 124, cur: 'USD', price: 13, + adomain: ['def.com'], w: 200, h: 100, ext: { @@ -409,6 +411,7 @@ describe('the spotx adapter', function () { expect(responses).to.be.an('array').with.length(2); expect(responses[0].cache_key).to.equal('cache123'); expect(responses[0].channel_id).to.equal(12345); + expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); expect(responses[0].cpm).to.equal(12); expect(responses[0].creativeId).to.equal(321); expect(responses[0].currency).to.equal('USD'); @@ -423,6 +426,7 @@ describe('the spotx adapter', function () { expect(responses[1].cache_key).to.equal('cache124'); expect(responses[1].channel_id).to.equal(12345); expect(responses[1].cpm).to.equal(13); + expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); expect(responses[1].creativeId).to.equal(''); expect(responses[1].currency).to.equal('USD'); expect(responses[1].height).to.equal(100); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index e15481d47e5..1521f4a2e63 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -175,6 +175,14 @@ describe('synacormediaBidAdapter ', function () { } }; + let bidderRequestWithCCPA = { + auctionId: 'xyz123', + refererInfo: { + referer: 'https://test.com/foo/bar' + }, + uspConsent: '1YYY' + }; + let expectedDataImp1 = { banner: { format: [ @@ -560,6 +568,18 @@ describe('synacormediaBidAdapter ', function () { } ]); }); + it('should contain the CCPA privacy string when UspConsent is in bidder request', function() { + // banner test + let req = spec.buildRequests([validBidRequest], bidderRequestWithCCPA); + expect(req).be.an('object'); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('https://prebid.technoratimedia.com/openrtb/bids/prebid?'); + expect(req.data).to.exist.and.to.be.an('object'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.regs.ext.us_privacy).to.equal('1YYY'); + expect(req.data.imp).to.eql([expectedDataImp1]); + }) }); describe('Bid Requests with schain object ', function() { @@ -668,21 +688,50 @@ describe('synacormediaBidAdapter ', function () { describe('interpretResponse', function () { let bidResponse = { id: '10865933907263896~9998~0', - impid: 'b9876abcd-300x250', + impid: 'b9876abcd', price: 0.13, crid: '1022-250', adm: '', - nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}' + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}', + w: 300, + h: 250 }; let bidResponse2 = { id: '10865933907263800~9999~0', - impid: 'b9876abcd-300x600', + impid: 'b9876abcd', price: 1.99, crid: '9993-013', adm: '', - nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}' + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}', + w: 300, + h: 600 }; + let bidRequest = { + data: { + id: '', + imp: [ + { + id: 'abc123', + banner: { + format: [ + { + w: 400, + h: 350 + } + ], + pos: 1 + } + } + ], + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; let serverResponse; beforeEach(function() { serverResponse = { @@ -697,6 +746,26 @@ describe('synacormediaBidAdapter ', function () { }); it('should return 1 video bid when 1 bid is in the video response', function () { + bidRequest = { + data: { + id: 'abcd1234', + imp: [ + { + video: { + w: 640, + h: 480 + }, + id: 'v2da7322b2df61f' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; let serverRespVideo = { body: { id: 'abcd1234', @@ -705,14 +774,16 @@ describe('synacormediaBidAdapter ', function () { bid: [ { id: '11339128001692337~9999~0', - impid: 'v2da7322b2df61f-640x480', + impid: 'v2da7322b2df61f', price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', adomain: [ 'psacentral.org' ], cid: 'bidder-crid', crid: 'bidder-cid', - cat: [] + cat: [], + w: 640, + h: 480 } ], seat: '9999' @@ -722,7 +793,7 @@ describe('synacormediaBidAdapter ', function () { }; // serverResponse.body.seatbid[0].bid.push(bidResponse); - let resp = spec.interpretResponse(serverRespVideo); + let resp = spec.interpretResponse(serverRespVideo, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '2da7322b2df61f', @@ -743,7 +814,7 @@ describe('synacormediaBidAdapter ', function () { it('should return 1 bid when 1 bid is in the response', function () { serverResponse.body.seatbid[0].bid.push(bidResponse); - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '9876abcd', @@ -766,7 +837,7 @@ describe('synacormediaBidAdapter ', function () { seat: '9999', bid: [bidResponse2], }); - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').to.have.lengthOf(2); expect(resp[0]).to.eql({ requestId: '9876abcd', @@ -798,7 +869,7 @@ describe('synacormediaBidAdapter ', function () { }); it('should not return a bid when no bid is in the response', function () { - let resp = spec.interpretResponse(serverResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); expect(resp).to.be.an('array').that.is.empty; }); @@ -817,14 +888,16 @@ describe('synacormediaBidAdapter ', function () { bid: [ { id: '11339128001692337~9999~0', - impid: 'v2da7322b2df61f-640x480', + impid: 'v2da7322b2df61f', price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', adomain: [ 'psacentral.org' ], cid: 'bidder-crid', crid: 'bidder-cid', - cat: [] + cat: [], + w: 640, + h: 480 } ], seat: '9999' @@ -840,10 +913,125 @@ describe('synacormediaBidAdapter ', function () { return config[key]; }); - let resp = spec.interpretResponse(serverRespVideo); + let resp = spec.interpretResponse(serverRespVideo, bidRequest); sandbox.restore(); expect(resp[0].videoCacheKey).to.not.exist; }); + + it('should use video bid request height and width if not present in response', function () { + bidRequest = { + data: { + id: 'abcd1234', + imp: [ + { + video: { + w: 300, + h: 250 + }, + id: 'v2da7322b2df61f' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; + + let serverRespVideo = { + body: { + id: 'abcd1234', + seatbid: [ + { + bid: [ + { + id: '11339128001692337~9999~0', + impid: 'v2da7322b2df61f', + price: 0.45, + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', + adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', + adomain: [ 'psacentral.org' ], + cid: 'bidder-crid', + crid: 'bidder-cid', + cat: [] + } + ], + seat: '9999' + } + ] + } + }; + let resp = spec.interpretResponse(serverRespVideo, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.eql({ + requestId: '2da7322b2df61f', + adId: '11339128001692337-9999-0', + cpm: 0.45, + width: 300, + height: 250, + creativeId: '9999_bidder-cid', + currency: 'USD', + netRevenue: true, + mediaType: 'video', + ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', + ttl: 60, + videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', + vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' + }); + }); + + it('should use banner bid request height and width if not present in response', function () { + bidRequest = { + data: { + id: 'abc123', + imp: [ + { + banner: { + format: [{ + w: 400, + h: 350 + }] + }, + id: 'babc123' + } + ] + }, + method: 'POST', + options: { + contentType: 'application/json', + withCredentials: true + }, + url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' + }; + + bidResponse = { + id: '10865933907263896~9998~0', + impid: 'babc123', + price: 0.13, + crid: '1022-250', + adm: '', + nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}', + }; + + serverResponse.body.seatbid[0].bid.push(bidResponse); + let resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.eql({ + requestId: 'abc123', + adId: '10865933907263896-9998-0', + cpm: 0.13, + width: 400, + height: 350, + creativeId: '9998_1022-250', + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: '', + ttl: 60 + }); + }); }); describe('getUserSyncs', function () { it('should return a usersync when iframes is enabled', function () { diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index cc5bbb840a9..17841b271d4 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -189,6 +189,34 @@ describe('teadsBidAdapter', () => { expect(payload.pageReferrer).to.deep.equal(document.referrer); }); + it('should add timeToFirstByte info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + const performance = window.performance || window.webkitPerformance || window.msPerformance || window.mozPerformance; + + const ttfbExpectedV2 = performance && + typeof performance.getEntriesByType === 'function' && + Object.prototype.toString.call(performance.getEntriesByType) === '[object Function]' && + performance.getEntriesByType('navigation')[0] && + performance.getEntriesByType('navigation')[0].responseStart && + performance.getEntriesByType('navigation')[0].requestStart && + performance.getEntriesByType('navigation')[0].responseStart >= 0 && + performance.getEntriesByType('navigation')[0].requestStart >= 0 && + Math.round( + performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart + ); + + expect(payload.timeToFirstByte).to.exist; + + if (ttfbExpectedV2) { + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpectedV2.toString()); + } else { + const ttfbExpectedV1 = performance.timing.responseStart - performance.timing.requestStart; + + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpectedV1.toString()); + } + }); + it('should send GDPR to endpoint with 11 status', function() { let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { diff --git a/test/spec/modules/telariaBidAdapter_spec.js b/test/spec/modules/telariaBidAdapter_spec.js index 9e4098d7854..25649115cc1 100644 --- a/test/spec/modules/telariaBidAdapter_spec.js +++ b/test/spec/modules/telariaBidAdapter_spec.js @@ -236,7 +236,7 @@ describe('TelariaAdapter', () => { it('should get correct bid response', () => { let expectedResponseKeys = ['bidderCode', 'width', 'height', 'statusMessage', 'adId', 'mediaType', 'source', 'getStatusCode', 'getSize', 'requestId', 'cpm', 'creativeId', 'vastXml', - 'vastUrl', 'currency', 'netRevenue', 'ttl', 'ad']; + 'vastUrl', 'currency', 'netRevenue', 'ttl', 'ad', 'meta']; let bidRequest = spec.buildRequests(stub, BIDDER_REQUEST)[0]; bidRequest.bidId = '1234'; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 73373293114..c6c3b622755 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -10,49 +10,88 @@ const GDPR_CONSENT_STR = 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7V describe('triplelift adapter', function () { const adapter = newBidder(tripleliftAdapterSpec); + let bid, instreamBid; - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { + this.beforeEach(() => { + bid = { bidder: 'triplelift', params: { inventoryCode: '12345', floor: 1.0, }, + mediaTypes: { + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + instreamBid = { + bidder: 'triplelift', + params: { + inventoryCode: 'insteam_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', }; + }) + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { it('should return true for valid bid request', function () { expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - inventoryCode: 'another_inv_code', - floor: 0.05 - }; + bid.params.inventoryCode = 'another_inv_code'; expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true when required params found - instream', function () { + expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(true); + }); + it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - floor: 1.0 - }; + delete bid.params.inventoryCode; expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when required params are not passed - instream', function () { + delete instreamBid.mediaTypes.playerSize; + delete instreamBid.params.video.w; + delete instreamBid.params.video.h; + expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -81,6 +120,14 @@ describe('triplelift adapter', function () { inventoryCode: '12345', floor: 1.0, }, + mediaTypes: { + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, adUnitCode: 'adunit-code', sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], bidId: '30b31c1838de1e', @@ -88,6 +135,33 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + }, + { + bidder: 'triplelift', + params: { + inventoryCode: 'insteam_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, } ]; @@ -103,6 +177,13 @@ describe('triplelift adapter', function () { height: 250, ad: 'ad-markup', iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + }, + { + imp_id: 1, + crid: '10092_76480_i2j6qm8u', + cpm: 0.01, + ad: 'The Trade Desk', + tlx_source: 'hdx' } ], refererInfo: { @@ -120,6 +201,11 @@ describe('triplelift adapter', function () { expect(request).to.exist.and.to.be.a('object'); }); + it('should be able find video object from the instream request', function () { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[1].video).to.exist.and.to.be.a('object'); + }); + it('should only parse sizes that are of the proper length and format', function () { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); expect(request.data.imp[0].banner.format).to.have.length(2); @@ -133,6 +219,9 @@ describe('triplelift adapter', function () { expect(payload.imp[0].tagid).to.equal('12345'); expect(payload.imp[0].floor).to.equal(1.0); expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + expect(payload.imp[1].tagid).to.equal('insteam_test'); + expect(payload.imp[1].floor).to.equal(1.0); + expect(payload.imp[1].video).to.exist.and.to.be.a('object'); }); it('should add tdid to the payload if included', function () { @@ -311,68 +400,9 @@ describe('triplelift adapter', function () { }); describe('interpretResponse', function () { - let response = { - body: { - bids: [ - { - imp_id: 0, - cpm: 1.062, - width: 300, - height: 250, - ad: 'ad-markup', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', - } - ] - } - }; - let bidderRequest = { - bidderCode: 'triplelift', - auctionId: 'a7ebcd1d-66ff-4b5c-a82c-6a21a6ee5a18', - bidderRequestId: '5c55612f99bc11', - bids: [ - { - imp_id: 0, - cpm: 1.062, - width: 300, - height: 250, - ad: 'ad-markup', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', - } - ], - refererInfo: { - referer: 'https://examplereferer.com' - }, - gdprConsent: { - consentString: GDPR_CONSENT_STR, - gdprApplies: true - } - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - requestId: '3db3773286ee59', - cpm: 1.062, - width: 300, - height: 250, - netRevenue: true, - ad: 'ad-markup', - creativeId: 29681110, - dealId: '', - currency: 'USD', - ttl: 33, - tl_source: 'tlx', - } - ]; - let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); - expect(result).to.have.length(1); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); - - it('should return multiple responses to support SRA', function () { - let response = { + let response, bidderRequest; + this.beforeEach(() => { + response = { body: { bids: [ { @@ -385,18 +415,16 @@ describe('triplelift adapter', function () { tl_source: 'tlx', }, { - imp_id: 0, - cpm: 1.9, - width: 300, - height: 600, - ad: 'ad-markup-2', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', + imp_id: 1, + crid: '10092_76480_i2j6qm8u', + cpm: 9.99, + ad: 'The Trade Desk', + tlx_source: 'hdx' } ] } }; - let bidderRequest = { + bidderRequest = { bidderCode: 'triplelift', auctionId: 'a7ebcd1d-66ff-4b5c-a82c-6a21a6ee5a18', bidderRequestId: '5c55612f99bc11', @@ -405,19 +433,33 @@ describe('triplelift adapter', function () { imp_id: 0, cpm: 1.062, width: 300, - height: 600, + height: 250, ad: 'ad-markup', iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', tl_source: 'tlx', + mediaTypes: { + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + bidId: '30b31c1838de1e', }, { - imp_id: 0, - cpm: 1.9, - width: 300, - height: 250, - ad: 'ad-markup-2', - iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', - tl_source: 'tlx', + imp_id: 1, + crid: '10092_76480_i2j6qm8u', + cpm: 9.99, + ad: 'The Trade Desk', + tlx_source: 'hdx', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', } ], refererInfo: { @@ -428,6 +470,46 @@ describe('triplelift adapter', function () { gdprApplies: true } }; + }) + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '30b31c1838de1e', + cpm: 1.062, + width: 300, + height: 250, + netRevenue: true, + ad: 'ad-markup', + creativeId: 29681110, + dealId: '', + currency: 'USD', + ttl: 33, + tl_source: 'tlx', + }, + { + requestId: '30b31c1838de1e', + cpm: 1.062, + width: 300, + height: 250, + netRevenue: true, + ad: 'The Trade Desk', + creativeId: 29681110, + dealId: '', + currency: 'USD', + ttl: 33, + tl_source: 'hdx', + mediaType: 'video', + vastXml: 'The Trade Desk', + } + ]; + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result).to.have.length(2); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[1])).to.have.members(Object.keys(expectedResponse[1])); + }); + + it('should return multiple responses to support SRA', function () { let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); expect(result).to.have.length(2); }); diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index e4218019e0d..72321ce415b 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -24,13 +24,49 @@ const invalidBidReq = { auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' }; -const bidReq = [{ +const videoBidReq = [{ adUnitCode: 'div-gpt-ad-1460505748561-0', bidder: BIDDER_CODE, params: { placementId: '10433394', + publisherId: 12345, + video: { + id: 123, + skippable: true, + playbackMethod: 2, + maxDuration: 30 + } + }, + mediaTypes: {video: { + context: 'outstream', + playerSize: [640, 480] + }}, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}, +{ + adUnitCode: 'div-gpt-ad-1460505748561-1', + bidder: BIDDER_CODE, + params: { + placementId: '10433395', publisherId: 12345 }, + mediaTypes: {video: { + context: 'outstream', + playerSize: [640, 480] + }}, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}]; +const bidReq = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345, + }, sizes: [[300, 250], [300, 600]], bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' @@ -147,6 +183,20 @@ const bidResArray = [ ttl: 360 } ]; +const bidVideoResponse = [ + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + mediaType: 'video', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; let element; let sandbox; @@ -241,6 +291,23 @@ describe('Undertone Adapter', () => { expect(bid2.publisherId).to.equal(12345); expect(bid2.params).to.be.an('object'); }); + it('should send video fields correctly', function () { + const request = spec.buildRequests(videoBidReq, bidderReq); + const bidVideo = JSON.parse(request.data)['x-ut-hb-params'][0]; + const bidVideo2 = JSON.parse(request.data)['x-ut-hb-params'][1]; + + expect(bidVideo.mediaType).to.equal('video'); + expect(bidVideo.video).to.be.an('object'); + expect(bidVideo.video.playerSize).to.be.an('array'); + expect(bidVideo.video.streamType).to.equal('outstream'); + expect(bidVideo.video.playbackMethod).to.equal(2); + expect(bidVideo.video.maxDuration).to.equal(30); + expect(bidVideo.video.skippable).to.equal(true); + + expect(bidVideo2.video.skippable).to.equal(null); + expect(bidVideo2.video.maxDuration).to.equal(null); + expect(bidVideo2.video.playbackMethod).to.equal(null); + }); it('should send all userIds data to server', function () { const request = spec.buildRequests(bidReqUserIds, bidderReq); const bidCommons = JSON.parse(request.data)['commons']; @@ -303,6 +370,13 @@ describe('Undertone Adapter', () => { it('should only use valid bid responses', () => { expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); }); + + it('should detect video response', () => { + const videoResult = spec.interpretResponse({body: bidVideoResponse}); + const vbid = videoResult[0]; + + expect(vbid.mediaType).to.equal('video'); + }); }); describe('getUserSyncs', () => { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index d038074cb10..d9671aabc84 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -5,7 +5,9 @@ import { requestBidsHook, setSubmoduleRegistry, syncDelay, - coreStorage + coreStorage, + setStoredValue, + setStoredConsentData } from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; @@ -13,6 +15,8 @@ import * as utils from 'src/utils.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import {getGlobal} from 'src/prebidGlobal.js'; +import {setConsentConfig, requestBidsHook as consentManagementRequestBidsHook, resetConsentData} from 'modules/consentManagement.js'; +import {gdprDataHandler} from 'src/adapterManager.js'; import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; import {pubCommonIdSubmodule} from 'modules/pubCommonIdSystem.js'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; @@ -27,6 +31,7 @@ import {server} from 'test/mocks/xhr.js'; let assert = require('chai').assert; let expect = require('chai').expect; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; +const CONSENT_LOCAL_STORAGE_NAME = '_pbjs_userid_consent_data'; describe('User ID', function() { function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6, configArr7, configArr8) { @@ -86,6 +91,10 @@ describe('User ID', function() { localStorage.removeItem('_pubcid_optout'); }); + beforeEach(function() { + coreStorage.setCookie(CONSENT_LOCAL_STORAGE_NAME, '', EXPIRED_COOKIE_DATE); + }); + describe('Decorate Ad Units', function() { beforeEach(function() { coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -210,8 +219,9 @@ describe('User ID', function() { }); }); }); - // Because the cookie exists already, there should be no setCookie call by default - expect(coreStorage.setCookie.callCount).to.equal(0); + // Because the cookie exists already, there should be no setCookie call by default; the only setCookie call is + // to store consent data + expect(coreStorage.setCookie.callCount).to.equal(1); }); it('Extend cookie', function() { @@ -236,8 +246,9 @@ describe('User ID', function() { }); }); }); - // Because extend is true, the cookie will be updated even if it exists already - expect(coreStorage.setCookie.callCount).to.equal(1); + // Because extend is true, the cookie will be updated even if it exists already. The second setCookie call + // is for storing consentData + expect(coreStorage.setCookie.callCount).to.equal(2); }); it('Disable auto create', function() { @@ -258,7 +269,8 @@ describe('User ID', function() { expect(bid).to.not.have.deep.nested.property('userIdAsEids'); }); }); - expect(coreStorage.setCookie.callCount).to.equal(0); + // setCookie is called once in order to store consentData + expect(coreStorage.setCookie.callCount).to.equal(1); }); it('pbjs.getUserIds', function() { @@ -660,7 +672,7 @@ describe('User ID', function() { it('does not delay auction if there are no ids to fetch', function() { coreStorage.getCookie.withArgs('MOCKID').returns('123456778'); config.setConfig({ - usersync: { + userSync: { auctionDelay: 33, syncDelay: 77, userIds: [{ @@ -1443,4 +1455,244 @@ describe('User ID', function() { expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); }); }); + + describe('Set cookie behavior', function() { + let coreStorageSpy; + beforeEach(function () { + coreStorageSpy = sinon.spy(coreStorage, 'setCookie'); + }); + afterEach(function () { + coreStorageSpy.restore(); + }); + it('should allow submodules to override the domain', function () { + const submodule = { + submodule: { + domainOverride: function () { + return 'foo.com' + } + }, + config: { + storage: { + type: 'cookie' + } + } + } + setStoredValue(submodule, 'bar'); + expect(coreStorage.setCookie.getCall(0).args[4]).to.equal('foo.com'); + }); + + it('should pass null for domain if submodule does not override the domain', function () { + const submodule = { + submodule: {}, + config: { + storage: { + type: 'cookie' + } + } + } + setStoredValue(submodule, 'bar'); + expect(coreStorage.setCookie.getCall(0).args[4]).to.equal(null); + }); + }); + + describe('Consent changes determine getId refreshes', function() { + let expStr; + let adUnits; + + const mockIdCookieName = 'MOCKID'; + let mockGetId = sinon.stub(); + let mockDecode = sinon.stub(); + let mockExtendId = sinon.stub(); + const mockIdSystem = { + name: 'mockId', + getId: mockGetId, + decode: mockDecode, + extendId: mockExtendId + }; + const userIdConfig = { + userSync: { + userIds: [{ + name: 'mockId', + storage: { + name: 'MOCKID', + type: 'cookie', + refreshInSeconds: 30 + } + }], + auctionDelay: 5 + } + }; + + let cmpStub; + let testConsentData; + const consentConfig = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: false + }; + + const sharedBeforeFunction = function() { + // clear cookies + expStr = (new Date(Date.now() + 25000).toUTCString()); + coreStorage.setCookie(mockIdCookieName, '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie(`${mockIdCookieName}_last`, '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie(CONSENT_LOCAL_STORAGE_NAME, '', EXPIRED_COOKIE_DATE); + + // init + adUnits = [getAdUnitMock()]; + init(config); + + // init id system + attachIdSystem(mockIdSystem); + config.setConfig(userIdConfig); + } + const sharedAfterFunction = function () { + config.resetConfig(); + mockGetId.reset(); + mockDecode.reset(); + mockExtendId.reset(); + cmpStub.restore(); + resetConsentData(); + delete window.__cmp; + delete window.__tcfapi; + }; + + describe('TCF v1', function() { + testConsentData = { + gdprApplies: true, + consentData: 'xyz', + apiVersion: 1 + }; + + beforeEach(function () { + sharedBeforeFunction(); + + // init v1 consent management + window.__cmp = function () { }; + delete window.__tcfapi; + cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { + args[2](testConsentData); + }); + setConsentConfig(consentConfig); + }); + + afterEach(function() { + sharedAfterFunction(); + }); + + it('does not call getId if no stored consent data and refresh is not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + + let innerAdUnits; + consentManagementRequestBidsHook(() => { }, {}); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + + sinon.assert.notCalled(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.calledOnce(mockExtendId); + + let consent = gdprDataHandler.getConsentData(); + let userIdStoredConsent = JSON.parse(coreStorage.getCookie(CONSENT_LOCAL_STORAGE_NAME)); + expect(userIdStoredConsent.gdprApplies).to.equal(consent.gdprApplies); + expect(userIdStoredConsent.gdprApplies).to.equal(testConsentData.gdprApplies); + expect(userIdStoredConsent.consentString).to.equal(consent.consentString); + expect(userIdStoredConsent.consentString).to.equal(testConsentData.consentData); + }); + + it('calls getId if no stored consent data but refresh is needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 60 * 1000).toUTCString()), expStr); + + let innerAdUnits; + consentManagementRequestBidsHook(() => { }, {}); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + + let consent = gdprDataHandler.getConsentData(); + let userIdStoredConsent = JSON.parse(coreStorage.getCookie(CONSENT_LOCAL_STORAGE_NAME)); + expect(userIdStoredConsent.gdprApplies).to.equal(consent.gdprApplies); + expect(userIdStoredConsent.gdprApplies).to.equal(testConsentData.gdprApplies); + expect(userIdStoredConsent.consentString).to.equal(consent.consentString); + expect(userIdStoredConsent.consentString).to.equal(testConsentData.consentData); + }); + + it('calls getId if empty stored consent and refresh not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + + setStoredConsentData(); + + let innerAdUnits; + consentManagementRequestBidsHook(() => { }, {}); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + + let consent = gdprDataHandler.getConsentData(); + let userIdStoredConsent = JSON.parse(coreStorage.getCookie(CONSENT_LOCAL_STORAGE_NAME)); + expect(userIdStoredConsent.gdprApplies).to.equal(consent.gdprApplies); + expect(userIdStoredConsent.gdprApplies).to.equal(testConsentData.gdprApplies); + expect(userIdStoredConsent.consentString).to.equal(consent.consentString); + expect(userIdStoredConsent.consentString).to.equal(testConsentData.consentData); + }); + + it('calls getId if stored consent does not match current consent and refresh not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + + setStoredConsentData({ + gdprApplies: testConsentData.gdprApplies, + consentString: 'abc', + apiVersion: testConsentData.apiVersion + }); + + let innerAdUnits; + consentManagementRequestBidsHook(() => { }, {}); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + + sinon.assert.calledOnce(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.notCalled(mockExtendId); + + let consent = gdprDataHandler.getConsentData(); + let userIdStoredConsent = JSON.parse(coreStorage.getCookie(CONSENT_LOCAL_STORAGE_NAME)); + expect(userIdStoredConsent.gdprApplies).to.equal(consent.gdprApplies); + expect(userIdStoredConsent.gdprApplies).to.equal(testConsentData.gdprApplies); + expect(userIdStoredConsent.consentString).to.equal(consent.consentString); + expect(userIdStoredConsent.consentString).to.equal(testConsentData.consentData); + }); + + it('does not call getId if stored consent matches current consent and refresh not needed', function () { + coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); + + setStoredConsentData({ + gdprApplies: testConsentData.gdprApplies, + consentString: testConsentData.consentData, + apiVersion: testConsentData.apiVersion + }); + + let innerAdUnits; + consentManagementRequestBidsHook(() => { }, {}); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + + sinon.assert.notCalled(mockGetId); + sinon.assert.calledOnce(mockDecode); + sinon.assert.calledOnce(mockExtendId); + + let consent = gdprDataHandler.getConsentData(); + let userIdStoredConsent = JSON.parse(coreStorage.getCookie(CONSENT_LOCAL_STORAGE_NAME)); + expect(userIdStoredConsent.gdprApplies).to.equal(consent.gdprApplies); + expect(userIdStoredConsent.gdprApplies).to.equal(testConsentData.gdprApplies); + expect(userIdStoredConsent.consentString).to.equal(consent.consentString); + expect(userIdStoredConsent.consentString).to.equal(testConsentData.consentData); + }); + }); + }); }); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index b8ab83a95ae..57e7f4e8ae2 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,7 +1,22 @@ import { expect } from 'chai'; -import { spec as adapter, SUPPORTED_ID_SYSTEMS, createDomain } from 'modules/vidazooBidAdapter.js'; +import { + spec as adapter, + SUPPORTED_ID_SYSTEMS, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, + getNextDealId, + getVidazooSessionId, +} from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; import { version } from 'package.json'; +import { useFakeTimers } from 'sinon'; const SUB_DOMAIN = 'openrtb'; @@ -64,10 +79,6 @@ const REQUEST = { } }; -const SYNC_OPTIONS = { - 'pixelEnabled': true -}; - describe('VidazooBidAdapter', function () { describe('validtae spec', function () { it('exists and is a function', function () { @@ -129,6 +140,7 @@ describe('VidazooBidAdapter', function () { }); it('should build request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.referer); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -146,6 +158,8 @@ describe('VidazooBidAdapter', function () { adUnitCode: 'div-gpt-ad-12345-0', publisherId: '59ac17c192832d0011283fe3', dealId: 1, + sessionId: '', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, bidderVersion: adapter.version, prebidVersion: version, res: `${window.top.screen.width}x${window.top.screen.height}`, @@ -220,7 +234,7 @@ describe('VidazooBidAdapter', function () { }); }); - describe(`user id system`, function () { + describe('user id system', function () { Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { const id = Date.now().toString(); const bid = utils.deepClone(BID); @@ -229,6 +243,7 @@ describe('VidazooBidAdapter', function () { switch (idSystemProvider) { case 'digitrustid': return { data: { id: id } }; case 'lipb': return { lipbid: id }; + case 'parrableId': return { eid: id }; default: return id; } })(); @@ -243,4 +258,119 @@ describe('VidazooBidAdapter', function () { }); }); }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({ 'c_id': '1' }); + const pid = extractPID({ 'p_id': '1' }); + const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({ 'cID': '1' }); + const pid = extractPID({ 'Pid': '2' }); + const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('vidazoo session id', function () { + it('should get undefined vidazoo session id', function () { + const sessionId = getVidazooSessionId(); + expect(sessionId).to.be.empty; + }); + + it('should get vidazoo session id from storage', function () { + const vidSid = '1234-5678'; + window.localStorage.setItem('vidSid', vidSid); + const sessionId = getVidazooSessionId(); + expect(sessionId).to.be.equal(vidSid); + }); + }); + + describe('deal id', function () { + const key = 'myDealKey'; + + it('should get the next deal id', function () { + const dealId = getNextDealId(key); + const nextDealId = getNextDealId(key); + expect(dealId).to.be.equal(1); + expect(nextDealId).to.be.equal(2); + }); + + it('should get the first deal id on expiration', function (done) { + setTimeout(function () { + const dealId = getNextDealId(key, 100); + expect(dealId).to.be.equal(1); + done(); + }, 200); + }); + }); + + describe('unique deal id', function () { + const key = 'myKey'; + let uniqueDealId; + + it('should get fresh unique deal id', function () { + const now = Date.now(); + uniqueDealId = getUniqueDealId(key); + expect(uniqueDealId).to.be.equal(`${key}_${now.toString()}`); + }); + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function () { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + }); + }); + + describe('storage utils', function () { + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const { value, created } = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({ event: 'send' }); + const { event } = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); }); diff --git a/test/spec/modules/waardexBidAdapter_spec.js b/test/spec/modules/waardexBidAdapter_spec.js index fae70335d3c..8732b2bd51f 100644 --- a/test/spec/modules/waardexBidAdapter_spec.js +++ b/test/spec/modules/waardexBidAdapter_spec.js @@ -10,12 +10,12 @@ describe('waardexBidAdapter', () => { params: { placementId: 1, traffic: 'banner', - pubId: 1, + zoneId: 1, } }; describe('isBidRequestValid', () => { - it('Should return true. bidId and params such as placementId and pubId are present', () => { + it('Should return true. bidId and params such as placementId and zoneId are present', () => { expect(spec.isBidRequestValid(validBid)).to.be.true; }); it('Should return false. bidId is not present in bid object', () => { @@ -23,14 +23,9 @@ describe('waardexBidAdapter', () => { delete invalidBid.bidId; expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); - it('Should return false. placementId is not present in bid.params object', () => { + it('Should return false. zoneId is not present in bid.params object', () => { const invalidBid = deepClone(validBid); - delete invalidBid.params.placementId; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; - }); - it('Should return false. pubId is not present in bid.params object', () => { - const invalidBid = deepClone(validBid); - delete invalidBid.params.pubId; + delete invalidBid.params.zoneId; expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); @@ -45,11 +40,10 @@ describe('waardexBidAdapter', () => { } }, params: { - placementId: 1, bidfloor: 1.5, position: 1, instl: 1, - pubId: 100 + zoneId: 100 }, }]; @@ -72,7 +66,6 @@ describe('waardexBidAdapter', () => { expect(payload.bidRequests[0]).deep.equal({ bidId: validBidRequests[0].bidId, - placementId: validBidRequests[0].params.placementId, bidfloor: validBidRequests[0].params.bidfloor, position: validBidRequests[0].params.position, instl: validBidRequests[0].params.instl, @@ -89,7 +82,7 @@ describe('waardexBidAdapter', () => { ], } }); - const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid?pubId=${validBidRequests[0].params.pubId}`; + const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid?pubId=${validBidRequests[0].params.zoneId}`; expect(url).to.equal(ENDPOINT); expect(method).to.equal('POST'); }); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 097f85b9b8d..1e20343c1cd 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -10,7 +10,8 @@ const REQUEST = { 'adSize': '728x90', 'targeting': { 'key1': 'value1', - 'key2': 'value2' + 'key2': 'value2', + 'notDoubleEncoded': 'value3,value4' }, 'customParams': { 'extraParam': true, @@ -84,8 +85,8 @@ describe('yieldlabBidAdapter', function () { expect(request.validBidRequests).to.eql([REQUEST]) }) - it('passes targeting to bid request', function () { - expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2') + it('passes single-encoded targeting to bid request', function () { + expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') }) it('passes userids to bid request', function () { @@ -96,6 +97,20 @@ describe('yieldlabBidAdapter', function () { expect(request.url).to.include('extraParam=true&foo=bar') }) + const refererRequest = spec.buildRequests(bidRequests, { + refererInfo: { + canonicalUrl: undefined, + numIframes: 0, + reachedTop: true, + referer: 'https://www.yieldlab.de/test?with=querystring', + stack: ['https://www.yieldlab.de/test?with=querystring'] + } + }) + + it('passes encoded referer to bid request', function () { + expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') + }) + const gdprRequest = spec.buildRequests(bidRequests, { gdprConsent: { consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 1c4c4812680..caeb26266fe 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -140,12 +140,13 @@ describe('YieldmoAdapter', function () { expect(data.hasOwnProperty('pr')).to.be.true; expect(data.hasOwnProperty('scrd')).to.be.true; expect(data.dnt).to.be.false; - expect(data.e).to.equal(90); expect(data.hasOwnProperty('description')).to.be.true; expect(data.hasOwnProperty('title')).to.be.true; expect(data.hasOwnProperty('h')).to.be.true; expect(data.hasOwnProperty('w')).to.be.true; expect(data.hasOwnProperty('pubcid')).to.be.true; + expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}'); + expect(data.us_privacy).to.equal(''); }); it('should add pubcid as parameter of request', function () { diff --git a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..24781d749e0 --- /dev/null +++ b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js @@ -0,0 +1,545 @@ +import yuktamediaAnalyticsAdapter from 'modules/yuktamediaAnalyticsAdapter.js'; +import { expect } from 'chai'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +let prebidAuction = { + 'auctionInit': { + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'timestamp': 1595850680304, + }, + 'bidRequested': { + 'bidderCode': 'appnexus', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'bidderRequestId': '181df4d465699c', + 'bids': [ + { + 'bidder': 'appnexus', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'userId': { + 'id5id': 'ID5-ZHMOxXeRXPe3inZKGD-Lj0g7y8UWdDbsYXQ_n6aWMQ', + 'parrableid': '01.1595590997.46d951017bdc272ca50b88dbcfb0545cfc636bec3e3d8c02091fb1b413328fb2fd3baf65cb4114b3f782895fd09f82f02c9042b85b42c4654d08ba06dc77f0ded936c8ea3fc4085b4a99', + 'pubcid': '100a8bc9-f588-4c22-873e-a721cb68bc34' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2bccebeda7fbe4', + 'bidderRequestId': '181df4d465699c', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1595850680304, + 'timeout': 1100, + 'start': 1595850680307 + }, + 'noBid': {}, + 'bidTimeout': [], + 'bidResponse': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'getStatusCode': function () { return 1; }, + 'adId': '3ade442375213f', + 'requestId': '2bccebeda7fbe4', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'responseTimestamp': 1595850681254, + 'requestTimestamp': 1595850680307, + 'bidder': 'appnexus', + 'timeToRespond': 947, + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '3ade442375213f', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + } + }, + 'auctionEnd': { + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954' + }, + 'bidWon': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'requestId': '2bccebeda7fbe4', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'ca421611-0bc0-4164-a69c-fe4158c68954', + 'responseTimestamp': 1595850681254, + 'requestTimestamp': 1595850680307, + 'bidder': 'appnexus', + 'timeToRespond': 947, + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '3ade442375213f', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered' + } +}; + +let prebidNativeAuction = { + 'auctionInit': { + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'timestamp': 1595589742100, + }, + 'bidRequested': { + 'bidderCode': 'appnexus', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'tid': 'f9c220eb-e44f-412b-92ff-8c24085ca675', + 'bids': [ + { + 'bidder': 'appnexus', + 'bid_id': '19a879bd73bc8d', + 'nativeParams': { + 'title': { + 'required': true, + 'len': 800 + }, + 'image': { + 'required': true, + 'sizes': [ + 989, + 742 + ] + }, + 'sponsoredBy': { + 'required': true + } + }, + 'mediaTypes': { + 'native': { + 'title': { + 'required': true, + 'len': 800 + }, + 'image': { + 'required': true, + 'sizes': [ + 989, + 742 + ] + }, + 'sponsoredBy': { + 'required': true + } + } + }, + 'adUnitCode': '/19968336/prebid_native_example_1', + 'sizes': [], + 'bidId': '19a879bd73bc8d', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 0, + 'bidderWinsCount': 0 + }, + { + 'bidder': 'appnexus', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '28f8cc7d10f2db', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 2, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1595589742100, + 'timeout': 1000, + 'src': 's2s', + 'start': 1595589742108 + }, + 'bidRequested1': { + 'bidderCode': 'ix', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'bidderRequestId': '5e64168f3654af', + 'bids': [ + { + 'bidder': 'ix', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'dfp-ad-rightrail_top', + 'sizes': [[300, 250]], + 'bidId': '9424dea605368f', + 'bidderRequestId': '5e64168f3654af', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s' + } + ], + 'auctionStart': 1595589742100, + 'timeout': 1000, + 'src': 's2s', + 'start': 1595589742108 + }, + 'noBid': { + 'bidder': 'ix', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'dfp-ad-rightrail_top', + 'transactionId': 'd99d90e0-663a-459d-8c87-4c92ce6a527c', + 'sizes': [[300, 250]], + 'bidId': '9424dea605368f', + 'bidderRequestId': '5e64168f3654af', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'src': 's2s', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 4, + 'bidderWinsCount': 0 + }, + 'bidTimeout': [ + { + 'bidId': '28f8cc7d10f2db', + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128' + } + ], + 'bidResponse': { + 'bidderCode': 'appnexus', + 'statusMessage': 'Bid available', + 'source': 's2s', + 'getStatusCode': function () { return 1; }, + 'cpm': 10, + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_pb': '10.00', + 'hb_adid': '4e756c72ee9044', + 'hb_size': 'undefinedxundefined', + 'hb_source': 's2s', + 'hb_format': 'native', + 'hb_native_linkurl': 'some_long_url', + 'hb_native_title': 'This is a Prebid Native Creative', + 'hb_native_brand': 'Prebid.org' + }, + 'native': { + 'clickUrl': { + 'url': 'some_long_url' + }, + 'impressionTrackers': [ + 'some_long_url' + ], + 'javascriptTrackers': [], + 'image': { + 'url': 'some_long_image_path', + 'width': 989, + 'height': 742 + }, + 'title': 'This is a Prebid Native Creative', + 'sponsoredBy': 'Prebid.org' + }, + 'currency': 'USD', + 'ttl': 60, + 'netRevenue': true, + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'responseTimestamp': 1595589742827, + 'requestTimestamp': 1595589742108, + 'bidder': 'appnexus', + 'adUnitCode': '/19968336/prebid_native_example_1', + 'timeToRespond': 719, + 'size': 'undefinedxundefined' + }, + 'auctionEnd': { + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128' + }, + 'bidWon': { + 'bidderCode': 'appnexus', + 'mediaType': 'native', + 'source': 's2s', + 'getStatusCode': function () { return 1; }, + 'cpm': 10, + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_pb': '10.00', + 'hb_adid': '4e756c72ee9044', + 'hb_size': 'undefinedxundefined', + 'hb_source': 's2s', + 'hb_format': 'native', + 'hb_native_linkurl': 'some_long_url', + 'hb_native_title': 'This is a Prebid Native Creative', + 'hb_native_brand': 'Prebid.org' + }, + 'native': { + 'clickUrl': { + 'url': 'some_long_url' + }, + 'impressionTrackers': [ + 'some_long_url' + ], + 'javascriptTrackers': [], + 'image': { + 'url': 'some_long_image_path', + 'width': 989, + 'height': 742 + }, + 'title': 'This is a Prebid Native Creative', + 'sponsoredBy': 'Prebid.org' + }, + 'currency': 'USD', + 'ttl': 60, + 'netRevenue': true, + 'auctionId': '86e005fa-1900-4782-b6df-528500f09128', + 'responseTimestamp': 1595589742827, + 'requestTimestamp': 1595589742108, + 'bidder': 'appnexus', + 'adUnitCode': '/19968336/prebid_native_example_1', + 'timeToRespond': 719, + 'size': 'undefinedxundefined', + 'status': 'rendered' + } +} + +describe('yuktamedia analytics adapter', function () { + beforeEach(() => { + sinon.stub(events, 'getEvents').returns([]); + }); + afterEach(() => { + events.getEvents.restore(); + }); + + describe('enableAnalytics', function () { + beforeEach(() => { + sinon.spy(yuktamediaAnalyticsAdapter, 'track'); + }); + afterEach(() => { + yuktamediaAnalyticsAdapter.disableAnalytics(); + yuktamediaAnalyticsAdapter.track.restore(); + }); + + it('should catch all events', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 6); + }); + + it('should catch no events if no pubKey and pubId', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + + sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 0); + }); + + it('should catch nobid, timeout and biwon event events', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidNativeAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED + '1']); + events.emit(constants.EVENTS.NO_BID, prebidNativeAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidNativeAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidNativeAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.AUCTION_END]); + events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.BID_WON]); + sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 8); + }); + }); + + describe('build utm tag data', function () { + beforeEach(function () { + localStorage.setItem('yuktamediaAnalytics_utm_source', 'prebid'); + localStorage.setItem('yuktamediaAnalytics_utm_medium', 'ad'); + localStorage.setItem('yuktamediaAnalytics_utm_campaign', ''); + localStorage.setItem('yuktamediaAnalytics_utm_term', ''); + localStorage.setItem('yuktamediaAnalytics_utm_content', ''); + localStorage.setItem('yuktamediaAnalytics_utm_timeout', Date.now()); + }); + + afterEach(function () { + localStorage.clear(); + }); + + it('should build utm data from local storage', function () { + let utmTagData = yuktamediaAnalyticsAdapter.buildUtmTagData({ + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + }); + expect(utmTagData.utm_source).to.equal('prebid'); + expect(utmTagData.utm_medium).to.equal('ad'); + expect(utmTagData.utm_campaign).to.equal(''); + expect(utmTagData.utm_term).to.equal(''); + expect(utmTagData.utm_content).to.equal(''); + }); + + it('should return empty object for disabled utm setting', function () { + let utmTagData = yuktamediaAnalyticsAdapter.buildUtmTagData({ + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: false, + enableSession: true, + enableUserIdCollection: true + }); + expect(utmTagData).deep.equal({}); + }); + }); + + describe('build session information', function () { + beforeEach(() => { + sinon.spy(yuktamediaAnalyticsAdapter, 'track'); + localStorage.clear(); + }); + afterEach(() => { + yuktamediaAnalyticsAdapter.disableAnalytics(); + yuktamediaAnalyticsAdapter.track.restore(); + localStorage.clear(); + }); + + it('should create session id in local storage if enabled', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + expect(localStorage.getItem('yuktamediaAnalytics_session_id')).to.not.equal(null); + }); + + it('should not create session id in local storage if disabled', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: false, + enableUserIdCollection: true + } + }); + events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + expect(localStorage.getItem('yuktamediaAnalytics_session_id')).to.equal(null); + }); + }); +}); diff --git a/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js deleted file mode 100644 index 24a524c85c7..00000000000 --- a/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js +++ /dev/null @@ -1,788 +0,0 @@ -import yuktamediaAnalyticsAdapter from 'modules/yuktamediaAnalyticsAdapter.js'; -import { expect } from 'chai'; -import adapterManager from 'src/adapterManager.js'; -import * as utils from 'src/utils.js'; -import { server } from 'test/mocks/xhr.js'; - -let events = require('src/events'); -let constants = require('src/constants.json'); - -describe('yuktamedia analytics adapter', function () { - beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - }); - - afterEach(function () { - events.getEvents.restore(); - }); - - describe('track', function () { - let initOptions = { - pubId: '1', - pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==' - }; - - let prebidEvent = { - 'addAdUnits': {}, - 'requestBids': {}, - 'auctionInit': { - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'timestamp': 1576823893836, - 'auctionStatus': 'inProgress', - 'adUnits': [ - { - 'code': 'div-gpt-ad-1460505748561-0', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - } - } - ], - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98' - } - ], - 'adUnitCodes': [ - 'div-gpt-ad-1460505748561-0' - ], - 'bidderRequests': [ - { - 'bidderCode': 'appnexus', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'bidderRequestId': '155975c76e13b1', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '263efc09896d0c', - 'bidderRequestId': '155975c76e13b1', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1576823893836, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] - }, - 'start': 1576823893838 - } - ], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 1000 - }, - 'bidRequested': { - 'bidderCode': 'appnexus', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'bidderRequestId': '155975c76e13b1', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '263efc09896d0c', - 'bidderRequestId': '155975c76e13b1', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1576823893836, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] - }, - 'start': 1576823893838 - }, - 'bidAdjustment': { - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '393976d8770041', - 'requestId': '263efc09896d0c', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'meta': { - 'advertiserId': 2529885 - }, - 'ad': '', - 'originalCpm': 0.5, - 'originalCurrency': 'USD', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'responseTimestamp': 1576823894050, - 'requestTimestamp': 1576823893838, - 'bidder': 'appnexus', - 'timeToRespond': 212 - }, - 'bidTimeout': [ - ], - 'bidResponse': { - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '393976d8770041', - 'requestId': '263efc09896d0c', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'meta': { - 'advertiserId': 2529885 - }, - 'ad': '', - 'originalCpm': 0.5, - 'originalCurrency': 'USD', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'responseTimestamp': 1576823894050, - 'requestTimestamp': 1576823893838, - 'bidder': 'appnexus', - 'timeToRespond': 212, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.50', - 'pbAg': '0.50', - 'pbDg': '0.50', - 'pbCg': '', - 'size': '300x250', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '393976d8770041', - 'hb_pb': '0.50', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner' - } - }, - 'auctionEnd': { - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'timestamp': 1576823893836, - 'auctionEnd': 1576823894054, - 'auctionStatus': 'completed', - 'adUnits': [ - { - 'code': 'div-gpt-ad-1460505748561-0', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - } - } - ], - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98' - } - ], - 'adUnitCodes': [ - 'div-gpt-ad-1460505748561-0' - ], - 'bidderRequests': [ - { - 'bidderCode': 'appnexus', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'bidderRequestId': '155975c76e13b1', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '263efc09896d0c', - 'bidderRequestId': '155975c76e13b1', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1576823893836, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] - }, - 'start': 1576823893838 - } - ], - 'noBids': [], - 'bidsReceived': [ - { - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '393976d8770041', - 'requestId': '263efc09896d0c', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'meta': { - 'advertiserId': 2529885 - }, - 'ad': '', - 'originalCpm': 0.5, - 'originalCurrency': 'USD', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'responseTimestamp': 1576823894050, - 'requestTimestamp': 1576823893838, - 'bidder': 'appnexus', - 'timeToRespond': 212, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.50', - 'pbAg': '0.50', - 'pbDg': '0.50', - 'pbCg': '', - 'size': '300x250', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '393976d8770041', - 'hb_pb': '0.50', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner' - } - } - ], - 'winningBids': [], - 'timeout': 1000 - }, - 'setTargeting': { - 'div-gpt-ad-1460505748561-0': { - 'hb_format': 'banner', - 'hb_source': 'client', - 'hb_size': '300x250', - 'hb_pb': '0.50', - 'hb_adid': '393976d8770041', - 'hb_bidder': 'appnexus', - 'hb_format_appnexus': 'banner', - 'hb_source_appnexus': 'client', - 'hb_size_appnexus': '300x250', - 'hb_pb_appnexus': '0.50', - 'hb_adid_appnexus': '393976d8770041', - 'hb_bidder_appnexus': 'appnexus' - } - }, - 'bidderDone': { - 'bidderCode': 'appnexus', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'bidderRequestId': '155975c76e13b1', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '263efc09896d0c', - 'bidderRequestId': '155975c76e13b1', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1576823893836, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] - }, - 'start': 1576823893838 - }, - 'bidWon': { - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '393976d8770041', - 'requestId': '263efc09896d0c', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'meta': { - 'advertiserId': 2529885 - }, - 'ad': '', - 'originalCpm': 0.5, - 'originalCurrency': 'USD', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'responseTimestamp': 1576823894050, - 'requestTimestamp': 1576823893838, - 'bidder': 'appnexus', - 'timeToRespond': 212, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.50', - 'pbAg': '0.50', - 'pbDg': '0.50', - 'pbCg': '', - 'size': '300x250', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '393976d8770041', - 'hb_pb': '0.50', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner' - }, - 'status': 'rendered', - 'params': [ - { - 'placementId': 13144370 - } - ] - } - }; - let location = utils.getWindowLocation(); - - let expectedAfterBid = { - 'bids': [ - { - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'bidId': '263efc09896d0c', - 'bidderCode': 'appnexus', - 'cpm': 0.5, - 'creativeId': 96846035, - 'currency': 'USD', - 'mediaType': 'banner', - 'netRevenue': true, - 'renderStatus': 2, - 'requestId': '155975c76e13b1', - 'requestTimestamp': 1576823893838, - 'responseTimestamp': 1576823894050, - 'sizes': '300x250,300x600', - 'statusMessage': 'Bid available', - 'timeToRespond': 212 - } - ], - 'auctionInit': { - 'host': location.host, - 'path': location.pathname, - 'search': location.search, - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'timestamp': 1576823893836, - 'auctionStatus': 'inProgress', - 'adUnits': [ - { - 'code': 'div-gpt-ad-1460505748561-0', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - } - } - ], - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98' - } - ], - 'adUnitCodes': [ - 'div-gpt-ad-1460505748561-0' - ], - 'bidderRequests': [ - { - 'bidderCode': 'appnexus', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'bidderRequestId': '155975c76e13b1', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'crumbs': { - 'pubcid': 'ff4002c4-ce05-4a61-b4ef-45a3cd93991a' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '6d275806-1943-4f3e-9cd5-624cbd05ad98', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '263efc09896d0c', - 'bidderRequestId': '155975c76e13b1', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1576823893836, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://observer.com/integrationExamples/gpt/hello_world.html', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'http://observer.com/integrationExamples/gpt/hello_world.html' - ] - }, - 'start': 1576823893838 - } - ], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 1000, - 'config': initOptions - }, - 'initOptions': initOptions - }; - - let expectedAfterBidWon = { - 'bidWon': { - 'bidderCode': 'appnexus', - 'bidId': '263efc09896d0c', - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', - 'creativeId': 96846035, - 'currency': 'USD', - 'cpm': 0.5, - 'netRevenue': true, - 'renderedSize': '300x250', - 'mediaType': 'banner', - 'statusMessage': 'Bid available', - 'status': 'rendered', - 'renderStatus': 4, - 'timeToRespond': 212, - 'requestTimestamp': 1576823893838, - 'responseTimestamp': 1576823894050 - }, - 'initOptions': { - 'pubId': '1', - 'pubKey': 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==' - } - } - - adapterManager.registerAnalyticsAdapter({ - code: 'yuktamedia', - adapter: yuktamediaAnalyticsAdapter - }); - - beforeEach(function () { - adapterManager.enableAnalytics({ - provider: 'yuktamedia', - options: initOptions - }); - }); - - afterEach(function () { - yuktamediaAnalyticsAdapter.disableAnalytics(); - }); - - it('builds and sends auction data', function () { - // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); - - // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); - - // Step 3: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); - - // Step 4: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); - - // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); - - expect(server.requests.length).to.equal(1); - - let realAfterBid = JSON.parse(server.requests[0].requestBody); - - expect(realAfterBid).to.deep.equal(expectedAfterBid); - - // Step 6: Send auction bid won event - events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); - - expect(server.requests.length).to.equal(2); - - let winEventData = JSON.parse(server.requests[1].requestBody); - - expect(winEventData).to.deep.equal(expectedAfterBidWon); - }); - }); -}); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index cab0655a29d..692cf9a6475 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -401,8 +401,12 @@ describe('bidders created by newBidder', function () { adUnitCode: 'mock/placement', currency: 'USD', netRevenue: true, - ttl: 300 + ttl: 300, + bidderCode: 'sampleBidder', + sampleBidder: {advertiserId: '12345', networkId: '111222'} }; + const bidderRequest = Object.assign({}, MOCK_BIDS_REQUEST); + bidderRequest.bids[0].bidder = 'sampleBidder'; spec.isBidRequestValid.returns(true); spec.buildRequests.returns({ method: 'POST', @@ -413,7 +417,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); @@ -423,6 +427,8 @@ describe('bidders created by newBidder', function () { expect(bidObject.originalCurrency).to.equal(bid.currency); expect(doneStub.calledOnce).to.equal(true); expect(logErrorSpy.callCount).to.equal(0); + expect(bidObject.meta).to.exist; + expect(bidObject.meta).to.deep.equal({advertiserId: '12345', networkId: '111222'}); }); it('should call spec.getUserSyncs() with the response', function () { @@ -645,6 +651,28 @@ describe('registerBidder', function () { expect(registerBidAdapterStub.secondCall.args[1]).to.equal('foo') expect(registerBidAdapterStub.thirdCall.args[1]).to.equal('bar') }); + + it('should register alias with their gvlid', function() { + const aliases = [ + { + code: 'foo', + gvlid: 1 + }, + { + code: 'bar', + gvlid: 2 + }, + { + code: 'baz' + } + ] + const thisSpec = Object.assign(newEmptySpec(), { aliases: aliases }); + registerBidder(thisSpec); + + expect(registerBidAdapterStub.getCall(1).args[0].getSpec().gvlid).to.equal(1); + expect(registerBidAdapterStub.getCall(2).args[0].getSpec().gvlid).to.equal(2); + expect(registerBidAdapterStub.getCall(3).args[0].getSpec().gvlid).to.equal(undefined); + }) }) describe('validate bid response: ', function () { diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index a5da1c904f0..3aae6e3c33a 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { targeting as targetingInstance, filters, sortByDealAndPriceBucketOrCpm } from 'src/targeting.js'; +import { targeting as targetingInstance, filters, getHighestCpmBidsFromBidPool, sortByDealAndPriceBucketOrCpm } from 'src/targeting.js'; import { config } from 'src/config.js'; import { getAdUnits, createBidReceived } from 'test/fixtures/fixtures.js'; import CONSTANTS from 'src/constants.json'; @@ -338,11 +338,44 @@ describe('targeting tests', function () { bid4 = utils.deepClone(bid1); bid4.adserverTargeting['hb_bidder'] = bid4.bidder = bid4.bidderCode = 'appnexus'; bid4.cpm = 2.25; + bid4.adId = '8383838'; enableSendAllBids = true; bidsReceived.push(bid4); }); + it('when sendBidsControl.bidLimit is set greater than 0 in getHighestCpmBidsFromBidPool', function () { + config.setConfig({ + sendBidsControl: { + bidLimit: 2, + dealPrioritization: true + } + }); + + const bids = getHighestCpmBidsFromBidPool(bidsReceived, utils.getHighestCpm, 2); + + expect(bids.length).to.equal(3); + expect(bids[0].adId).to.equal('8383838'); + expect(bids[1].adId).to.equal('148018fe5e'); + expect(bids[2].adId).to.equal('48747745'); + }); + + it('when sendBidsControl.bidLimit is set greater than 0 and deal priortization is false in getHighestCpmBidsFromBidPool', function () { + config.setConfig({ + sendBidsControl: { + bidLimit: 2, + dealPrioritization: false + } + }); + + const bids = getHighestCpmBidsFromBidPool(bidsReceived, utils.getHighestCpm, 2); + + expect(bids.length).to.equal(3); + expect(bids[0].adId).to.equal('8383838'); + expect(bids[1].adId).to.equal('148018fe5e'); + expect(bids[2].adId).to.equal('48747745'); + }); + it('selects the top n number of bids when enableSendAllBids is true and and bitLimit is set', function () { config.setConfig({ sendBidsControl: { @@ -369,7 +402,7 @@ describe('targeting tests', function () { expect(limitedBids.length).to.equal(2); }); - it('Sends all bids when enableSendAllBids is true and and bitLimit is set to 0', function () { + it('Sends all bids when enableSendAllBids is true and and bidLimit is set to 0', function () { config.setConfig({ sendBidsControl: { bidLimit: 0 @@ -690,171 +723,171 @@ describe('targeting tests', function () { describe('sortByDealAndPriceBucketOrCpm', function() { it('will properly sort bids when some bids have deals and some do not', function () { let bids = [{ - adUnitTargeting: { + adserverTargeting: { hb_adid: 'abc', hb_pb: '1.00', hb_deal: '1234' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'def', hb_pb: '0.50', } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'ghi', hb_pb: '20.00', hb_deal: '4532' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'jkl', hb_pb: '9.00', hb_deal: '9864' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'mno', hb_pb: '50.00', } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'pqr', hb_pb: '100.00', } }]; bids.sort(sortByDealAndPriceBucketOrCpm()); - expect(bids[0].adUnitTargeting.hb_adid).to.equal('ghi'); - expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); - expect(bids[2].adUnitTargeting.hb_adid).to.equal('abc'); - expect(bids[3].adUnitTargeting.hb_adid).to.equal('pqr'); - expect(bids[4].adUnitTargeting.hb_adid).to.equal('mno'); - expect(bids[5].adUnitTargeting.hb_adid).to.equal('def'); + expect(bids[0].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('pqr'); + expect(bids[4].adserverTargeting.hb_adid).to.equal('mno'); + expect(bids[5].adserverTargeting.hb_adid).to.equal('def'); }); it('will properly sort bids when all bids have deals', function () { let bids = [{ - adUnitTargeting: { + adserverTargeting: { hb_adid: 'abc', hb_pb: '1.00', hb_deal: '1234' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'def', hb_pb: '0.50', hb_deal: '4321' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'ghi', hb_pb: '2.50', hb_deal: '4532' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'jkl', hb_pb: '2.00', hb_deal: '9864' } }]; bids.sort(sortByDealAndPriceBucketOrCpm()); - expect(bids[0].adUnitTargeting.hb_adid).to.equal('ghi'); - expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); - expect(bids[2].adUnitTargeting.hb_adid).to.equal('abc'); - expect(bids[3].adUnitTargeting.hb_adid).to.equal('def'); + expect(bids[0].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('def'); }); it('will properly sort bids when no bids have deals', function () { let bids = [{ - adUnitTargeting: { + adserverTargeting: { hb_adid: 'abc', hb_pb: '1.00' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'def', hb_pb: '0.10' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'ghi', hb_pb: '10.00' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'jkl', hb_pb: '10.01' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'mno', hb_pb: '1.00' } }, { - adUnitTargeting: { + adserverTargeting: { hb_adid: 'pqr', hb_pb: '100.00' } }]; bids.sort(sortByDealAndPriceBucketOrCpm()); - expect(bids[0].adUnitTargeting.hb_adid).to.equal('pqr'); - expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); - expect(bids[2].adUnitTargeting.hb_adid).to.equal('ghi'); - expect(bids[3].adUnitTargeting.hb_adid).to.equal('abc'); - expect(bids[4].adUnitTargeting.hb_adid).to.equal('mno'); - expect(bids[5].adUnitTargeting.hb_adid).to.equal('def'); + expect(bids[0].adserverTargeting.hb_adid).to.equal('pqr'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[4].adserverTargeting.hb_adid).to.equal('mno'); + expect(bids[5].adserverTargeting.hb_adid).to.equal('def'); }); it('will properly sort bids when some bids have deals and some do not and by cpm when flag is set to true', function () { let bids = [{ cpm: 1.04, - adUnitTargeting: { + adserverTargeting: { hb_adid: 'abc', hb_pb: '1.00', hb_deal: '1234' } }, { cpm: 0.50, - adUnitTargeting: { + adserverTargeting: { hb_adid: 'def', hb_pb: '0.50', hb_deal: '4532' } }, { cpm: 0.53, - adUnitTargeting: { + adserverTargeting: { hb_adid: 'ghi', hb_pb: '0.50', hb_deal: '4532' } }, { cpm: 9.04, - adUnitTargeting: { + adserverTargeting: { hb_adid: 'jkl', hb_pb: '9.00', hb_deal: '9864' } }, { cpm: 50.00, - adUnitTargeting: { + adserverTargeting: { hb_adid: 'mno', hb_pb: '50.00', } }, { cpm: 100.00, - adUnitTargeting: { + adserverTargeting: { hb_adid: 'pqr', hb_pb: '100.00', } }]; bids.sort(sortByDealAndPriceBucketOrCpm(true)); - expect(bids[0].adUnitTargeting.hb_adid).to.equal('jkl'); - expect(bids[1].adUnitTargeting.hb_adid).to.equal('abc'); - expect(bids[2].adUnitTargeting.hb_adid).to.equal('ghi'); - expect(bids[3].adUnitTargeting.hb_adid).to.equal('def'); - expect(bids[4].adUnitTargeting.hb_adid).to.equal('pqr'); - expect(bids[5].adUnitTargeting.hb_adid).to.equal('mno'); + expect(bids[0].adserverTargeting.hb_adid).to.equal('jkl'); + expect(bids[1].adserverTargeting.hb_adid).to.equal('abc'); + expect(bids[2].adserverTargeting.hb_adid).to.equal('ghi'); + expect(bids[3].adserverTargeting.hb_adid).to.equal('def'); + expect(bids[4].adserverTargeting.hb_adid).to.equal('pqr'); + expect(bids[5].adserverTargeting.hb_adid).to.equal('mno'); }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 8a142d7a6aa..960ccf08c92 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1770,8 +1770,6 @@ describe('Unit: Prebid Module', function () { }); describe('multiformat requests', function () { - let spyCallBids; - let createAuctionStub; let adUnits; beforeEach(function () { @@ -1791,14 +1789,10 @@ describe('Unit: Prebid Module', function () { }]; adUnitCodes = ['adUnit-code']; configObj.setConfig({maxRequestsPerOrigin: Number.MAX_SAFE_INTEGER || 99999999}); - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - spyCallBids = sinon.spy(adapterManager, 'callBids'); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); + sinon.spy(adapterManager, 'callBids'); }) afterEach(function () { - auctionModule.newAuction.restore(); adapterManager.callBids.restore(); }); @@ -1821,7 +1815,6 @@ describe('Unit: Prebid Module', function () { const spyArgs = adapterManager.callBids.getCall(0); const biddersCalled = spyArgs.args[0][0].bids; - // only appnexus supports native expect(biddersCalled.length).to.equal(1); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 4dbb40557af..fca59633ebe 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1115,4 +1115,67 @@ describe('Utils', function () { }); }); }); + + describe('deepEqual', function() { + it('should return "true" if comparing the same object', function() { + const obj1 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + const obj2 = obj1; + expect(utils.deepEqual(obj1, obj2)).to.equal(true); + }); + it('should return "true" if two deeply nested objects are equal', function() { + const obj1 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + const obj2 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + expect(utils.deepEqual(obj1, obj2)).to.equal(true); + }); + it('should return "true" if comparting the same primitive values', function() { + const primitive1 = 'Prebid.js'; + const primitive2 = 'Prebid.js'; + expect(utils.deepEqual(primitive1, primitive2)).to.equal(true); + }); + it('should return "false" if comparing two different primitive values', function() { + const primitive1 = 12; + const primitive2 = 123; + expect(utils.deepEqual(primitive1, primitive2)).to.equal(false); + }); + it('should return "false" if comparing two different deeply nested objects', function() { + const obj1 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] }, + ], + }, + }; + const obj2 = { + banner: { + sizeConfig: [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [1000, 0], sizes: [[1000, 300], [728, 90]] }, + ], + }, + } + expect(utils.deepEqual(obj1, obj2)).to.equal(false); + }); + }); });