diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cfb29ebdfa9..69e13850258 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,8 @@ ARG VARIANT="12" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg + # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0176b8317b3..104d9a38132 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "nickdodd79.gulptasks" + "nickdodd79.gulptasks", + "dbaeumer.vscode-eslint" ], // 9999 is web server, 9876 is karma diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 4a9502e38c1..69cf4c5fc7f 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@021a2405c7f990db57f5eae5397423dcc554159c + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: app_id: ${{ secrets.ISSUE_APP_ID }} private_key: ${{ secrets.ISSUE_APP_PEM }} @@ -29,21 +29,30 @@ jobs: gh api graphql -f query=' query($org: String!, $number: Int!) { organization(login: $org){ - projectNext(number: $number) { + projectV2(number: $number) { id fields(first:100) { nodes { - id - name - settings + ... on ProjectV2Field { + id + name + } + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } } } } } }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json - echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV - echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV + echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV + echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name=="'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV - name: Add issue to project env: @@ -52,9 +61,9 @@ jobs: run: | gh api graphql -f query=' mutation($project:ID!, $issue:ID!) { - addProjectNextItem(input: {projectId: $project, contentId: $issue}) { - projectNextItem { - id, + addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) { + item { + id content { ... on Issue { createdAt @@ -67,8 +76,8 @@ jobs: } }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json - echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV - echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_ID='$(jq '.data.addProjectV2ItemById.item.id' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectV2ItemById.item.content.createdAt' issue_data.json | cut -c 2-11) >> $GITHUB_ENV - name: Set fields env: @@ -79,15 +88,17 @@ jobs: $project: ID! $item: ID! $date_field: ID! - $date_value: String! + $date_value: Date! ) { - set_creation_date: updateProjectNextItemField(input: { + set_creation_date: updateProjectV2ItemFieldValue(input: { projectId: $project itemId: $item fieldId: $date_field - value: $date_value + value: { + date: $date_value + } }) { - projectNextItem { + projectV2Item { id } } diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html index fd61267479d..78fba71f774 100644 --- a/integrationExamples/gpt/adloox.html +++ b/integrationExamples/gpt/adloox.html @@ -147,8 +147,13 @@ realTimeData: { auctionDelay: AUCTION_DELAY, dataProviders: [ + { + name: 'intersection', + waitForIt: true + }, { name: 'adloox', + waitForIt: true, params: { // optional, defaults shown thresholds: [ 50, 60, 70, 80, 90 ], slotinpath: false diff --git a/integrationExamples/gpt/gpp_us_hello_world.html b/integrationExamples/gpt/gpp_us_hello_world.html new file mode 100644 index 00000000000..28be86127fc --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world.html @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+ +
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/gpp_us_hello_world_iframe.html b/integrationExamples/gpt/gpp_us_hello_world_iframe.html new file mode 100644 index 00000000000..c0a62f9d72e --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world_iframe.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html b/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html new file mode 100644 index 00000000000..8c2096d614d --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+ +
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html old mode 100755 new mode 100644 diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 8d44de00fa2..1d6bfb8424e 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -1,4 +1,4 @@ -import {deepSetValue, mergeDeep} from '../../../src/utils.js'; +import {deepSetValue, logWarn, mergeDeep} from '../../../src/utils.js'; import {bannerResponseProcessor, fillBannerImp} from './banner.js'; import {fillVideoImp, fillVideoResponse} from './video.js'; import {setResponseMediaType} from './mediaType.js'; @@ -20,6 +20,10 @@ export const DEFAULT_PROCESSORS = { appFpd: fpdFromTopLevelConfig('app'), siteFpd: fpdFromTopLevelConfig('site'), deviceFpd: fpdFromTopLevelConfig('device'), + onlyOneClient: { + priority: -99, + fn: onlyOneClientSection + }, props: { // sets request properties id, tmax, test, source.tid fn(ortbRequest, bidderRequest) { @@ -133,3 +137,17 @@ function fpdFromTopLevelConfig(prop) { } } } + +export function onlyOneClientSection(ortbRequest) { + ['dooh', 'app', 'site'].reduce((found, section) => { + if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { + if (found != null) { + logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) + delete ortbRequest[section]; + } else { + found = section; + } + } + return found; + }, null); +} diff --git a/libraries/pbsExtensions/processors/adUnitCode.js b/libraries/pbsExtensions/processors/adUnitCode.js new file mode 100644 index 00000000000..f936e0f662f --- /dev/null +++ b/libraries/pbsExtensions/processors/adUnitCode.js @@ -0,0 +1,13 @@ +import {deepSetValue} from '../../../src/utils.js'; + +export function setImpAdUnitCode(imp, bidRequest) { + const adUnitCode = bidRequest.adUnitCode; + + if (adUnitCode) { + deepSetValue( + imp, + `ext.prebid.adunitcode`, + adUnitCode + ); + } +} diff --git a/libraries/pbsExtensions/processors/pbs.js b/libraries/pbsExtensions/processors/pbs.js index 56a2a391c76..0ed2d12fad8 100644 --- a/libraries/pbsExtensions/processors/pbs.js +++ b/libraries/pbsExtensions/processors/pbs.js @@ -3,6 +3,7 @@ import {deepAccess, isPlainObject, isStr, mergeDeep} from '../../../src/utils.js import {extPrebidMediaType} from './mediaType.js'; import {setRequestExtPrebidAliases} from './aliases.js'; import {setImpBidParams} from './params.js'; +import {setImpAdUnitCode} from './adUnitCode.js'; import {setRequestExtPrebid, setRequestExtPrebidChannel} from './requestExtPrebid.js'; import {setBidResponseVideoCache} from './video.js'; @@ -26,6 +27,10 @@ export const PBS_PROCESSORS = { // sets bid ext.prebid.bidder.[bidderCode] with bidRequest.params, passed through transformBidParams if necessary fn: setImpBidParams }, + adUnitCode: { + // sets bid ext.prebid.adunitcode + fn: setImpAdUnitCode + } }, [BID_RESPONSE]: { mediaType: { diff --git a/modules/.submodules.json b/modules/.submodules.json index c3016914f1b..a535fd4988d 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -55,9 +55,11 @@ "aaxBlockmeterRtdProvider", "airgridRtdProvider", "akamaiDapRtdProvider", + "arcspanRtdProvider", "blueconicRtdProvider", "browsiRtdProvider", "captifyRtdProvider", + "confiantRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", "hadronRtdProvider", diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index d9524a281f8..e9901794ff9 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -167,7 +167,8 @@ function buildRequests(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } = _buildRequestParams(bidRequests, bidderRequest); const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); @@ -181,6 +182,7 @@ function buildRequests(bidRequests, bidderRequest) { gdprConsent, uspConsent, pageUrl, + referer, ttxSettings }) ) @@ -199,7 +201,9 @@ function _buildRequestParams(bidRequests, bidderRequest) { const uspConsent = bidderRequest && bidderRequest.uspConsent; - const pageUrl = bidderRequest?.refererInfo?.page + const pageUrl = bidderRequest?.refererInfo?.page; + + const referer = bidderRequest?.refererInfo?.ref; adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); @@ -207,7 +211,8 @@ function _buildRequestParams(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } } @@ -241,7 +246,7 @@ function _getMRAKey(bidRequest) { } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, referer, ttxSettings }) { const ttxRequest = {}; const firstBidRequest = bidRequests[0]; const { siteId, test } = firstBidRequest.params; @@ -262,6 +267,10 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU ttxRequest.site.page = pageUrl; } + if (referer) { + ttxRequest.site.ref = referer; + } + ttxRequest.id = firstBidRequest.auctionId; if (gdprConsent.consentString) { diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 4f86a914982..12fd50c6636 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -669,10 +669,8 @@ function autoFillParams(bid) { } // extra params - setExtraParam(bid, 'environment'); setExtraParam(bid, 'pagetype'); setExtraParam(bid, 'category'); - setExtraParam(bid, 'subcategory'); } function getPageDimensions() { @@ -1094,8 +1092,6 @@ export const spec = { bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; bidObj.category = bidReq.params.category; - bidObj.subcategory = bidReq.params.subcategory; - bidObj.environment = bidReq.params.environment; } bidResponses.push(bidObj); }); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index 9b48caa8233..b804a72fea9 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -17,12 +17,10 @@ Below, the list of Adagio params and where they can be set. | Param name | Global config | AdUnit config | | ---------- | ------------- | ------------- | | siteId | x | -| organizationId (obsolete) | | x -| site (obsolete) | | x +| organizationId * | | x +| site * | | x | pagetype | x | x -| environment | x | x | category | x | x -| subcategory | x | x | useAdUnitCodeAsAdUnitElementId | x | x | useAdUnitCodeAsPlacement | x | x | placement | | x @@ -31,6 +29,8 @@ Below, the list of Adagio params and where they can be set. | video | | x | native | | x +_* These params are deprecated in favor the Global configuration setup, see below._ + ### Global configuration The global configuration is used to store params once instead of duplicate them to each adUnit. The values will be used as "params" in the ad-request. @@ -49,9 +49,7 @@ pbjs.setConfig({ // - underscores `_` // Also, each param can have at most 50 unique active values (case-insensitive). pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. - environment: 'mobile', // Recommended. Environment where the page is displayed. category: 'sport', // Recommended. Category of the content displayed in the page. - subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value }, @@ -62,9 +60,7 @@ pbjs.setConfig({ Adagio will use FPD data as fallback for the params below: - pagetype -- environment - category -- subcategory If the FPD value is an array, the 1st value of this array will be used. @@ -107,9 +103,7 @@ var adUnits = [ debug: true, adagio: { pagetype: 'article', - environment: 'mobile', category: 'sport', - subcategory: 'handball', useAdUnitCodeAsAdUnitElementId: false, useAdUnitCodeAsPlacement: false, } @@ -208,12 +202,6 @@ var adUnits = [ return bidResponse.site; } }, - { - key: "environment", - val: function (bidResponse) { - return bidResponse.environment; - } - }, { key: "placement", val: function (bidResponse) { @@ -231,12 +219,6 @@ var adUnits = [ val: function (bidResponse) { return bidResponse.category; } - }, - { - key: "subcategory", - val: function (bidResponse) { - return bidResponse.subcategory; - } } ] } diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index b0449418a87..5736c7c19ba 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -97,7 +97,8 @@ export const spec = { {code: 'motionspots'}, {code: 'sonic_twist'}, {code: 'displayioads'}, - {code: 'rtbdemand_com'} + {code: 'rtbdemand_com'}, + {code: 'bidbuddy'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index 203b118652e..d77ee25ab5f 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -34,19 +34,21 @@ When tracking video you have two options: To view an [example of an Adloox integration](../integrationExamples/gpt/adloox.html): - gulp serve --nolint --notest --modules=gptPreAuction,categoryTranslation,dfpAdServerVideo,rtdModule,instreamTracking,rubiconBidAdapter,spotxBidAdapter,adlooxAnalyticsAdapter,adlooxAdServerVideo,adlooxRtdProvider + gulp serve --nolint --notest --modules=gptPreAuction,categoryTranslation,dfpAdServerVideo,intersectionRtdProvider,rtdModule,instreamTracking,rubiconBidAdapter,spotxBidAdapter,adlooxAnalyticsAdapter,adlooxAdServerVideo,adlooxRtdProvider **N.B.** `categoryTranslation` is required by `dfpAdServerVideo` that otherwise causes a JavaScript console warning +**N.B.** `intersectionRtdProvider` is used by `adlooxRtdProvider` to provide (above-the-fold) ATF measurement, if not enabled the `atf` segment will not be available + Now point your browser at: http://localhost:9999/integrationExamples/gpt/adloox.html?pbjs_debug=true ### Public Example -The example is published publically at: https://storage.googleapis.com/adloox-ads-js-test/prebid.html?pbjs_debug=true +The example is published publicly at: https://storage.googleapis.com/adloox-ads-js-test/prebid.html?pbjs_debug=true **N.B.** this will show a [CORS error](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors) for the request `https://p.adlooxtracking.com/q?...` that is safe to ignore on the public example page; it is related to the [RTD integration](./adlooxRtdProvider.md) which requires pre-registration of your sites -It is recommended you use [Google Chrome's 'Local Overrides' located in the Developer Tools panel](https://www.trysmudford.com/blog/chrome-local-overrides/) to explore the example without the inconvience of having to run your own web server. +It is recommended you use [Google Chrome's 'Local Overrides' located in the Developer Tools panel](https://www.trysmudford.com/blog/chrome-local-overrides/) to explore the example without the inconvenience of having to run your own web server. #### Pre-built `prebid.js` diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index 8862ac8ac47..c2037429185 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -6,11 +6,13 @@ * @module modules/adlooxRtdProvider * @requires module:modules/realTimeData * @requires module:modules/adlooxAnalyticsAdapter + * @optional module:modules/intersectionRtdProvider */ /* eslint standard/no-callback-literal: "off" */ /* eslint prebid/validate-imports: "off" */ +import {auctionManager} from '../src/auctionManager.js'; import {command as analyticsCommand, COMMAND} from './adlooxAnalyticsAdapter.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; @@ -18,142 +20,31 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {getRefererInfo} from '../src/refererDetection.js'; import { _each, + _map, + buildUrl, deepAccess, + deepClone, deepSetValue, - getAdUnitSizes, getGptSlotInfoForAdUnitCode, isArray, isBoolean, isInteger, isPlainObject, - isStr, logError, logInfo, logWarn, - mergeDeep + mergeDeep, + parseUrl, + safeJSONParse } from '../src/utils.js'; -import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'adloox'; const MODULE = `${MODULE_NAME}RtdProvider`; const API_ORIGIN = 'https://p.adlooxtracking.com'; const SEGMENT_HISTORIC = { 'a': 'aud', 'd': 'dis', 'v': 'vid' }; -const SEGMENT_HISTORIC_VALUES = Object.keys(SEGMENT_HISTORIC).map(k => SEGMENT_HISTORIC[k]); -const ADSERVER_TARGETING_PREFIX = 'adl_'; - -const CREATIVE_WIDTH_MIN = 20; -const CREATIVE_HEIGHT_MIN = 20; -const CREATIVE_AREA_MIN = CREATIVE_WIDTH_MIN * CREATIVE_HEIGHT_MIN; -// try to avoid using IntersectionObserver as it has unbounded (observed multi-second) latency -let intersectionObserver = window == top ? false : undefined; -const intersectionObserverElements = []; -// .map/.findIndex are safe here as they are only used for intersectionObserver -function atf(adUnit, cb) { - analyticsCommand(COMMAND.TOSELECTOR, { bid: { adUnitCode: adUnit.code } }, function(selector) { - const element = document.querySelector(selector); - if (!element) return cb(null); - - if (window.getComputedStyle(element)['display'] == 'none') return cb(NaN); - - try { - // short circuit for cross-origin - if (intersectionObserver) throw false; - - // Google's advice is to collapse slots on no fill but - // we have to cater to clients that grow slots on fill - const rect = (function(rect) { - const sizes = getAdUnitSizes(adUnit); - - if (sizes.length == 0) return false; - // interstitial (0x0, 1x1) - if (sizes.length == 1 && (sizes[0][0] * sizes[0][1]) <= 1) return true; - // try to catch premium slots (coord=0,0) as they will likely bounce into view - if (rect.top <= -window.pageYOffset && rect.left <= -window.pageXOffset && rect.top == rect.bottom) return true; - - // pick the smallest creative size as many publishers will just leave the element unbounded in the vertical - let width = Infinity; - let height = Infinity; - for (let i = 0; i < sizes.length; i++) { - const area = sizes[i][0] * sizes[i][1]; - if (area < CREATIVE_AREA_MIN) continue; - if (area < (width * height)) { - width = sizes[i][0]; - height = sizes[i][1]; - } - } - // we also scale the smallest size to the size of the slot as publishers resize units depending on viewport - const scale = Math.min(1, (rect.right - rect.left) / width); - - return { - left: rect.left, - right: rect.left + Math.max(CREATIVE_WIDTH_MIN, scale * width), - top: rect.top, - bottom: rect.top + Math.max(CREATIVE_HEIGHT_MIN, scale * height) - }; - })(element.getBoundingClientRect()); - - if (rect === false) return cb(NaN); - if (rect === true) return cb(1); - - const W = rect.right - rect.left; - const H = rect.bottom - rect.top; - - if (W * H < CREATIVE_AREA_MIN) return cb(NaN); - - let el; - let win = window; - while (1) { - // https://stackoverflow.com/a/8876069 - const vw = Math.max(win.document.documentElement.clientWidth || 0, win.innerWidth || 0); - const vh = Math.max(win.document.documentElement.clientHeight || 0, win.innerHeight || 0); - - // cut to viewport - rect.left = Math.min(Math.max(rect.left, 0), vw); - rect.right = Math.min(Math.max(rect.right, 0), vw); - rect.top = Math.min(Math.max(rect.top, 0), vh); - rect.bottom = Math.min(Math.max(rect.bottom, 0), vh); - - if (win == top) return cb(((rect.right - rect.left) * (rect.bottom - rect.top)) / (W * H)); - el = win.frameElement; - if (!el) throw false; // cross-origin - win = win.parent; - - // transpose to frame element - const frameElementRect = el.getBoundingClientRect(); - rect.left += frameElementRect.left; - rect.right = Math.min(rect.right + frameElementRect.left, frameElementRect.right); - rect.top += frameElementRect.top; - rect.bottom = Math.min(rect.bottom + frameElementRect.top, frameElementRect.bottom); - } - } catch (_) { - if (intersectionObserver === undefined) { - try { - intersectionObserver = new IntersectionObserver(function(entries) { - entries.forEach(entry => { - const ratio = entry.intersectionRect.width * entry.intersectionRect.height < CREATIVE_AREA_MIN ? NaN : entry.intersectionRatio; - const idx = intersectionObserverElements.findIndex(x => x.element == entry.target); - intersectionObserverElements[idx].cb.forEach(cb => cb(ratio)); - intersectionObserverElements.splice(idx, 1); - intersectionObserver.unobserve(entry.target); - }); - }); - } catch (_) { - intersectionObserver = false; - } - } - if (!intersectionObserver) return cb(null); - const idx = intersectionObserverElements.findIndex(x => x.element == element); - if (idx == -1) { - intersectionObserverElements.push({ element, cb: [ cb ] }); - intersectionObserver.observe(element); - } else { - intersectionObserverElements[idx].cb.push(cb); - } - } - }); -} +const ADSERVER_TARGETING_PREFIX = 'adl'; function init(config, userConsent) { logInfo(MODULE, 'init', config, userConsent); @@ -167,10 +58,6 @@ function init(config, userConsent) { logError(MODULE, 'invalid params'); return false; } - if (!(config.params.api_origin === undefined || isStr(config.params.api_origin))) { - logError(MODULE, 'invalid api_origin params value'); - return false; - } if (!(config.params.imps === undefined || (isInteger(config.params.imps) && config.params.imps > 0))) { logError(MODULE, 'invalid imps params value'); return false; @@ -213,193 +100,110 @@ function init(config, userConsent) { } function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ - const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits).map(adUnit => { - return { - gpid: deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot || adUnit.code, - unit: adUnit - }; - }).filter(adUnit => !!adUnit.gpid); - - let response = {}; - function setSegments() { - function val(v, k) { - if (!((SEGMENT_HISTORIC[k] || k == 'atf') && v >= 0)) return v; - return config.params.thresholds.filter(t => t <= v); - } - - const ortb2 = reqBidsConfigObj.ortb2Fragments?.global || {}; - const dataSite = deepAccess(ortb2, 'site.ext.data') || {}; - const dataUser = deepAccess(ortb2, 'user.ext.data') || {}; - - _each(response, (v0, k0) => { - if (k0 == '_') return; - const k = SEGMENT_HISTORIC[k0] || k0; - const v = val(v0, k0); - deepSetValue(k == k0 ? dataUser : dataSite, `${MODULE_NAME}_rtd.${k}`, v); - }); - deepSetValue(dataSite, `${MODULE_NAME}_rtd.ok`, true); + const adUnits0 = reqBidsConfigObj.adUnits || getGlobal().adUnits; + // adUnits must be ordered according to adUnitCodes for stable 's' param usage and handling the response below + const adUnits = reqBidsConfigObj.adUnitCodes.map(code => adUnits0.find(unit => unit.code == code)); + + // buildUrl creates PHP style multi-parameters and includes undefined... (╯°□°)╯ ┻━┻ + const url = buildUrl(mergeDeep(parseUrl(`${API_ORIGIN}/q`), { search: { + 'v': `pbjs-${getGlobal().version}`, + 'c': config.params.clientid, + 'p': config.params.platformid, + 't': config.params.tagid, + 'imp': config.params.imps, + 'fc_ip': config.params.freqcap_ip, + 'fc_ipua': config.params.freqcap_ipua, + 'pn': (getRefererInfo().page || '').substr(0, 300).split(/[?#]/)[0], + 's': _map(adUnits, function(unit) { + // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ + const gpid = deepAccess(unit, 'ortb2Imp.ext.gpid') || + deepAccess(unit, 'ortb2Imp.ext.data.pbadslot') || + getGptSlotInfoForAdUnitCode(unit.code).gptSlot || + unit.code; + const ref = [ gpid ]; + if (!config.params.slotinpath) ref.push(unit.code); + return ref.join('\t'); + }) + } })).replace(/\[\]|[^?&]+=undefined/g, '').replace(/([?&])&+/g, '$1'); + + ajax(url, + function(responseText, q) { + function val(v, k) { + if (!(SEGMENT_HISTORIC[k] && v >= 0)) return v; + return config.params.thresholds.filter(t => t <= v); + } - deepSetValue(ortb2, 'site.ext.data', dataSite); - deepSetValue(ortb2, 'user.ext.data', dataUser); - deepSetValue(reqBidsConfigObj, 'ortb2Fragments.global', ortb2); + const response = safeJSONParse(responseText); + if (!response) { + logError(MODULE, 'unexpected response'); + return callback(); + } - adUnits.forEach((adUnit, i) => { - _each(response['_'][i], (v0, k0) => { + const { site: ortb2site, user: ortb2user } = reqBidsConfigObj.ortb2Fragments.global; + _each(response, function(v0, k0) { + if (k0 == '_') return; const k = SEGMENT_HISTORIC[k0] || k0; const v = val(v0, k0); - deepSetValue(adUnit.unit, `ortb2Imp.ext.data.${MODULE_NAME}_rtd.${k}`, v); + deepSetValue(k == k0 ? ortb2user : ortb2site, `ext.data.${MODULE_NAME}_rtd.${k}`, v); }); - }); - }; - - // mergeDeep does not handle merging deep arrays... (╯°□°)╯ ┻━┻ - function mergeDeep(target, ...sources) { - function emptyValue(v) { - if (isPlainObject(v)) { - return {}; - } else if (isArray(v)) { - return []; - } else { - return undefined; - } - } - if (!sources.length) return target; - const source = sources.shift(); - - if (isPlainObject(target) && isPlainObject(source)) { - Object.keys(source).forEach(key => { - if (!(key in target)) target[key] = emptyValue(source[key]); - target[key] = target[key] !== undefined ? mergeDeep(target[key], source[key]) : source[key]; - }); - } else if (isArray(target) && isArray(source)) { - source.forEach((v, i) => { - if (!(i in target)) target[i] = emptyValue(v); - target[i] = target[i] !== undefined ? mergeDeep(target[i], v) : v; + _each(response._, function(segments, i) { + _each(segments, function(v0, k0) { + const k = SEGMENT_HISTORIC[k0] || k0; + const v = val(v0, k0); + deepSetValue(adUnits[i], `ortb2Imp.ext.data.${MODULE_NAME}_rtd.${k}`, v); + }); }); - } else { - target = source; - } - return mergeDeep(target, ...sources); - } + deepSetValue(ortb2site, `ext.data.${MODULE_NAME}_rtd.ok`, true); - let semaphore = 1; - function semaphoreInc(inc) { - if (semaphore == 0) return; - semaphore += inc; - if (semaphore == 0) { - setSegments() callback(); } - } - - const refererInfo = getRefererInfo(); - const args = [ - [ 'v', `pbjs-${getGlobal().version}` ], - [ 'c', config.params.clientid ], - [ 'p', config.params.platformid ], - [ 't', config.params.tagid ], - [ 'imp', config.params.imps ], - [ 'fc_ip', config.params.freqcap_ip ], - [ 'fc_ipua', config.params.freqcap_ipua ], - [ 'pn', (refererInfo.page || '').substr(0, 300).split(/[?#]/)[0] ] - ]; - - if (!adUnits.length) { - logWarn(MODULE, 'no suitable adUnits (missing pbadslot?)'); - } - const atfQueue = []; - adUnits.map((adUnit, i) => { - const ref = [ adUnit.gpid ]; - if (!config.params.slotinpath) ref.push(adUnit.unit.code); - args.push(['s', ref.join('\t')]); - - semaphoreInc(1); - atfQueue.push(function() { - atf(adUnit.unit, function(x) { - let viewable = document.visibilityState === undefined || document.visibilityState == 'visible'; - try { viewable = viewable && top.document.hasFocus() } catch (_) {} - logInfo(MODULE, `atf code=${adUnit.unit.code} has area=${x}, viewable=${viewable}`); - const atfList = []; atfList[i] = { atf: parseInt(x * 100) }; - response = mergeDeep(response, { _: atfList }); - semaphoreInc(-1); - }); - }); - }); - function atfCb() { - atfQueue.forEach(x => x()); - } - if (document.readyState == 'loading') { - document.addEventListener('DOMContentLoaded', atfCb, false); - } else { - atfCb(); - } - - analyticsCommand(COMMAND.URL, { - url: (config.params.api_origin || API_ORIGIN) + '/q?', - args: args - }, function(url) { - ajax(url, { - success: function(responseText, q) { - try { - if (q.getResponseHeader('content-type') == 'application/json') { - response = mergeDeep(response, JSON.parse(responseText)); - } else { - throw false; - } - } catch (_) { - logError(MODULE, 'unexpected response'); - } - semaphoreInc(-1); - }, - error: function(statusText, q) { - logError(MODULE, 'request failed'); - semaphoreInc(-1); - } - }); - }); + ); } function getTargetingData(adUnitArray, config, userConsent, auction) { - function targetingNormalise(v) { + function val(v) { if (isArray(v) && v.length == 0) return undefined; if (isBoolean(v)) v = ~~v; if (!v) return undefined; // empty string and zero return v; } - const ortb2 = auction.getFPD().global || {}; - const dataSite = deepAccess(ortb2, `site.ext.data.${MODULE_NAME}_rtd`) || {}; - if (!dataSite.ok) return {}; + const { site: ortb2site, user: ortb2user } = auctionManager.index.getAuction(auction).getFPD().global; - const dataUser = deepAccess(ortb2, `user.ext.data.${MODULE_NAME}_rtd`) || {}; - return getGlobal().adUnits.filter(adUnit => includes(adUnitArray, adUnit.code)).reduce((a, adUnit) => { - a[adUnit.code] = {}; + const ortb2base = {}; + _each(deepAccess(mergeDeep(ortb2site, ortb2user), `ext.data.${MODULE_NAME}_rtd`), function(v0, k) { + const v = val(v0); + if (v) ortb2base[`${ADSERVER_TARGETING_PREFIX}_${k}`] = v; + }); - _each(dataSite, (v0, k) => { - if (includes(SEGMENT_HISTORIC_VALUES, k)) return; // ignore site average viewability - const v = targetingNormalise(v0); - if (v) a[adUnit.code][ADSERVER_TARGETING_PREFIX + k] = v; - }); + const targeting = {}; + _each(auction.adUnits.filter(unit => adUnitArray.includes(unit.code)), function(unit) { + targeting[unit.code] = deepClone(ortb2base); - const adUnitSegments = deepAccess(adUnit, `ortb2Imp.ext.data.${MODULE_NAME}_rtd`, {}); - _each(Object.assign({}, dataUser, adUnitSegments), (v0, k) => { - const v = targetingNormalise(v0); - if (v) a[adUnit.code][ADSERVER_TARGETING_PREFIX + k] = v; + const ortb2imp = deepAccess(unit, `ortb2Imp.ext.data.${MODULE_NAME}_rtd`); + _each(ortb2imp, function(v0, k) { + const v = val(v0); + if (v) targeting[unit.code][`${ADSERVER_TARGETING_PREFIX}_${k}`] = v; }); - return a; - }, {}); + // ATF results shamelessly exfiltrated from intersectionRtdProvider + const bid = unit.bids.find(bid => !!bid.intersection); + if (bid) { + const v = val(config.params.thresholds.filter(t => t <= (bid.intersection.intersectionRatio * 100))); + if (v) targeting[unit.code][`${ADSERVER_TARGETING_PREFIX}_atf`] = v; + } + }); + + return targeting; } export const subModuleObj = { name: MODULE_NAME, init, getBidRequestData, - getTargetingData, - atf // used by adlooxRtdProvider_spec.js + getTargetingData }; submodule('realTimeData', subModuleObj); diff --git a/modules/adlooxRtdProvider.md b/modules/adlooxRtdProvider.md index 466f8ed1ba2..9d6a20a01a7 100644 --- a/modules/adlooxRtdProvider.md +++ b/modules/adlooxRtdProvider.md @@ -19,12 +19,7 @@ This provider fetches segments and populates the [First Party Data](https://docs * AdUnit segments are placed into `AdUnit.ortb2Imp.ext.data.adloox_rtd`: * **`{dis,vid,aud}`:** an list of integers describing the likelihood the AdUnit will be visible * **`atf`:** an list of integers describing the percentage of pixels visible at auction - * measured only once at pre-auction - * usable when the publisher uses the strategy of collapsing ad slots on no-fill - * using the reverse strategy, growing ad slots on fill, invalidates the measurement the position of all content (including the slots) changes post-auction - * works best when your page loads your ad slots have their actual size rendered (ie. not zero height) - * uses the smallest ad unit (above a threshold area of 20x20) supplied by the [publisher to Prebid.js](https://docs.prebid.org/dev-docs/examples/basic-example.html) and measures viewability as if that size to be used - * when used in cross-origin (unfriendly) IFRAME environments the ad slot is directly measured as is (ignoring publisher provided sizes) due to limitations in using [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) + * measured at pre-auction time using the [Intersection Module](https://docs.prebid.org/dev-docs/modules/intersectionRtdProvider.html); if not enabled then this measurement is not available **N.B.** this provider does not offer or utilise any user orientated data @@ -57,8 +52,13 @@ To use this, you *must* also integrate the [Adloox Analytics Adapter](./adlooxAn realTimeData: { auctionDelay: 100, // see below for guidance dataProviders: [ + { + name: 'intersection', + waitForIt: true + }, { name: 'adloox', + waitForIt: true, params: { // optional, defaults shown thresholds: [ 50, 60, 70, 80, 90 ], slotinpath: false diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 148424c0a98..808c788fcb9 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,5 +1,6 @@ import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; @@ -35,7 +36,7 @@ export const spec = { const bids = validBidRequests.map(buildRequestObject); const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); - const currency = getValue(validBidRequests[0].params, 'currency') || 'TRY'; + const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; const bidderName = validBidRequests[0].bidder; const payload = { diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 90031ed7f5d..8dbcfdafd32 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,12 +1,14 @@ -import { logError } from '../src/utils.js'; +import {logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'admixer'; -const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'smn']; +const BIDDER_CODE_ADX = 'admixeradx'; +const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'admixeradx']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; +const ADX_ENDPOINT_URL = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; export const spec = { code: BIDDER_CODE, aliases: ALIASES, @@ -57,6 +59,10 @@ export const spec = { if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + let bidFloor = getBidFloor(bidderRequest); + if (bidFloor) { + payload.bidFloor = bidFloor; + } } validRequest.forEach((bid) => { let imp = {}; @@ -65,7 +71,11 @@ export const spec = { }); return { method: 'POST', - url: endpointUrl || ENDPOINT_URL, + url: + endpointUrl || + (bidderRequest.bidderCode === BIDDER_CODE_ADX + ? ADX_ENDPOINT_URL + : ENDPOINT_URL), data: payload, }; }, @@ -96,4 +106,16 @@ export const spec = { return pixels; } }; +function getBidFloor(bid) { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0; + } +} registerBidder(spec); diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 5ad5436e732..ea3b723b316 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; -import { isStr, deepAccess, logInfo } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { isStr, deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -8,7 +8,7 @@ const BIDDER_CODE = 'adnuntius'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' -const DEFAULT_NATIVE = 'native' +// const DEFAULT_NATIVE = 'native' const checkSegment = function (segment) { if (isStr(segment)) return segment; @@ -28,31 +28,31 @@ const getSegmentsFromOrtb = function (ortb2) { return segments } -function createNative(ad) { - const native = {}; - const assets = ad.assets - native.title = ad.text.title.content; - native.image = { - url: assets.image.cdnId, - height: assets.image.height, - width: assets.image.width, - }; - if (assets.icon) { - native.icon = { - url: assets.icon.cdnId, - height: assets.icon.height, - width: assets.icon.width, - }; - } - - native.sponsoredBy = ad.text.sponsoredBy?.content || ''; - native.body = ad.text.body?.content || ''; - native.cta = ad.text.cta?.content || ''; - native.clickUrl = ad.destinationUrls.destination || ''; - native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; - - return native; -} +// function createNative(ad) { +// const native = {}; +// const assets = ad.assets +// native.title = ad.text.title.content; +// native.image = { +// url: assets.image.cdnId, +// height: assets.image.height, +// width: assets.image.width, +// }; +// if (assets.icon) { +// native.icon = { +// url: assets.icon.cdnId, +// height: assets.icon.height, +// width: assets.icon.width, +// }; +// } + +// native.sponsoredBy = ad.text.sponsoredBy?.content || ''; +// native.body = ad.text.body?.content || ''; +// native.cta = ad.text.cta?.content || ''; +// native.clickUrl = ad.destinationUrls.destination || ''; +// native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; + +// return native; +// } const handleMeta = function () { const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) @@ -73,7 +73,7 @@ const getUsi = function (meta, ortb2, bidderRequest) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { return !!(bid.bidId || (bid.params.member && bid.params.invCode)); }, @@ -109,9 +109,9 @@ export const spec = { network += '_video' } - if (bid.mediaTypes && bid.mediaTypes.native) { - network += '_native' - } + // if (bid.mediaTypes && bid.mediaTypes.native) { + // network += '_native' + // } bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); @@ -130,7 +130,7 @@ export const spec = { const network = networkKeys[j]; const networkRequest = [...request] if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } - if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } + // if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } requests.push({ method: 'POST', url: ENDPOINT_URL + '?' + networkRequest.join('&'), @@ -170,15 +170,13 @@ export const spec = { if (adUnit.vastXml) { adResponse[adUnit.targetId].vastXml = adUnit.vastXml adResponse[adUnit.targetId].mediaType = VIDEO - } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { - adResponse[adUnit.targetId].native = createNative(ad); - adResponse[adUnit.targetId].mediaType = NATIVE; + // } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { + // adResponse[adUnit.targetId].native = createNative(ad); + // adResponse[adUnit.targetId].mediaType = NATIVE; } else { adResponse[adUnit.targetId].ad = adUnit.html } - logInfo('BID', adResponse) - return adResponse } else return response }, {}); diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index f7473b3bad4..e6cdc7698bf 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -126,6 +126,8 @@ export const spec = { payload.userId = createEidsArray(bidderRequest.userId); } + payload.pbjs_version = '$prebid.version$'; + const data = JSON.stringify(payload); const options = { withCredentials: true diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 41bf680db1c..e4d5c618b77 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,4 +1,4 @@ -import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, logError, logInfo} from '../src/utils.js'; +import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, isNumber, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -21,6 +21,8 @@ export const ERROR_CODES = { SITE_ID_INVALID_VALUE: 4, MEDIA_TYPE_NOT_SUPPORTED: 5, PUBLISHER_ID_INVALID_VALUE: 6, + INVALID_RATELIMIT: 7, + PLACEMENT_ID_INVALID_VALUE: 8, }; const endpoints = { @@ -30,29 +32,29 @@ const endpoints = { timeout: `${BASE_URL}/notice/timeout`, error: `${BASE_URL}/notice/error`, } -} +}; export function setEndPoints(env = null, path = '', mediaType = BANNER) { switch (env) { case 'local': - endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest` - endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win` - endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error` - endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout` + endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout`; break; case 'main': - endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest` - endpoints.notice.win = `${BASE_URL}${path}/notice/win` - endpoints.notice.error = `${BASE_URL}${path}/notice/error` - endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout` + endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout`; break; } - return endpoints + return endpoints; } config.getConfig('aidem', function (config) { - if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType) } -}) + if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType); } +}); // AIDEM Custom FN function recur(obj) { @@ -121,8 +123,8 @@ function getDevice() { function getRegs() { let regs = {}; - const consentManagement = config.getConfig('consentManagement') - const coppa = config.getConfig('coppa') + const consentManagement = config.getConfig('consentManagement'); + const coppa = config.getConfig('coppa'); if (consentManagement && !!(consentManagement.gdpr)) { deepSetValue(regs, 'gdpr_applies', !!consentManagement.gdpr); } else { @@ -145,11 +147,15 @@ function getRegs() { } function getPageUrl(bidderRequest) { - return bidderRequest?.refererInfo?.page + return bidderRequest?.refererInfo?.page; } function buildWinNotice(bid) { + const params = bid.params[0]; return { + publisherId: params.publisherId, + siteId: params.siteId, + placementId: params.placementId, burl: deepAccess(bid, 'meta.burl'), cpm: bid.cpm, currency: bid.currency, @@ -161,7 +167,7 @@ function buildWinNotice(bid) { ttl: bid.ttl, requestTimestamp: bid.requestTimestamp, responseTimestamp: bid.responseTimestamp, - } + }; } function buildErrorNotice(prebidErrorResponse) { @@ -171,29 +177,29 @@ function buildErrorNotice(prebidErrorResponse) { auctionId: prebidErrorResponse.auctionId, bidderRequestId: prebidErrorResponse.bidderRequestId, metrics: {} - } + }; } function hasValidFloor(obj) { - if (!obj) return false - const hasValue = !isNaN(Number(obj.value)) - const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency) - return hasValue && hasCurrency + if (!obj) return false; + const hasValue = !isNaN(Number(obj.value)); + const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency); + return hasValue && hasCurrency; } function getMediaType(bidRequest) { - if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO } - return BANNER + if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO; } + return BANNER; } function getPrebidRequestFields(bidderRequest, bidRequests) { - const payload = {} + const payload = {}; // Base Payload Data deepSetValue(payload, 'id', bidderRequest.auctionId); // Impressions - setPrebidImpressionObject(bidRequests, payload) + setPrebidImpressionObject(bidRequests, payload); // Device - deepSetValue(payload, 'device', getDevice()) + deepSetValue(payload, 'device', getDevice()); // Timeout deepSetValue(payload, 'tmax', bidderRequest.timeout); // Currency @@ -203,13 +209,13 @@ function getPrebidRequestFields(bidderRequest, bidRequests) { // Privacy Regs deepSetValue(payload, 'regs', getRegs()); // Site - setPrebidSiteObject(bidderRequest, payload) + setPrebidSiteObject(bidderRequest, payload); // Environment - setPrebidRequestEnvironment(payload) + setPrebidRequestEnvironment(payload); // AT auction type deepSetValue(payload, 'at', 1); - return payload + return payload; } function setPrebidImpressionObject(bidRequests, payload) { @@ -220,22 +226,24 @@ function setPrebidImpressionObject(bidRequests, payload) { deepSetValue(impressionObject, 'id', bidRequest.bidId); // Transaction id deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'transactionId')); + // placement id + deepSetValue(impressionObject, 'tagid', deepAccess(bidRequest, 'params.placementId', null)); // Publisher id deepSetValue(payload, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); // Site id deepSetValue(payload, 'site.id', deepAccess(bidRequest, 'params.siteId')); - const mediaType = getMediaType(bidRequest) + const mediaType = getMediaType(bidRequest); switch (mediaType) { case 'banner': - setPrebidImpressionObjectBanner(bidRequest, impressionObject) + setPrebidImpressionObjectBanner(bidRequest, impressionObject); break; case 'video': - setPrebidImpressionObjectVideo(bidRequest, impressionObject) + setPrebidImpressionObjectVideo(bidRequest, impressionObject); break; } // Floor (optional) - setPrebidImpressionObjectFloor(bidRequest, impressionObject) + setPrebidImpressionObjectFloor(bidRequest, impressionObject); impressionObject.imp_ext = {}; @@ -265,13 +273,15 @@ function setPrebidRequestEnvironment(payload) { deepSetValue(payload, 'environment.inp.jp', window.JSON.parse.name === 'parse' && typeof window.JSON.parse.prototype === 'undefined'); deepSetValue(payload, 'environment.inp.ofe', window.Object.fromEntries.name === 'fromEntries' && typeof window.Object.fromEntries.prototype === 'undefined'); deepSetValue(payload, 'environment.inp.oa', window.Object.assign.name === 'assign' && typeof window.Object.assign.prototype === 'undefined'); + deepSetValue(payload, 'environment.wpar.innerWidth', window.innerWidth); + deepSetValue(payload, 'environment.wpar.innerHeight', window.innerHeight); } function setPrebidImpressionObjectFloor(bidRequest, impressionObject) { - const floor = deepAccess(bidRequest, 'params.floor') + const floor = deepAccess(bidRequest, 'params.floor'); if (hasValidFloor(floor)) { - deepSetValue(impressionObject, 'floor.value', floor.value) - deepSetValue(impressionObject, 'floor.currency', floor.currency) + deepSetValue(impressionObject, 'floor.value', floor.value); + deepSetValue(impressionObject, 'floor.currency', floor.currency); } } @@ -318,7 +328,7 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { deepSetValue(prebidResponseBidObject, 'currency', openRTBResponseBidObject.cur ? openRTBResponseBidObject.cur.toUpperCase() : DEFAULT_CURRENCY); deepSetValue(prebidResponseBidObject, 'width', openRTBResponseBidObject.w); deepSetValue(prebidResponseBidObject, 'height', openRTBResponseBidObject.h); - deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid) + deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid); deepSetValue(prebidResponseBidObject, 'netRevenue', true); deepSetValue(prebidResponseBidObject, 'ttl', 60000); @@ -331,13 +341,13 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { deepSetValue(prebidResponseBidObject, 'mediaType', BANNER); deepSetValue(prebidResponseBidObject, 'ad', openRTBResponseBidObject.adm); } - setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) - return prebidResponseBidObject + setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject); + return prebidResponseBidObject; } function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); - deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', openRTBResponseBidObject.adomain); + deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { const primaryCatId = openRTBResponseBidObject.cat.shift(); deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); @@ -353,28 +363,28 @@ function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponse } function hasValidMediaType(bidRequest) { - const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest) + const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest); if (!supported) { logError('AIDEM Bid Adapter: media type not supported', { bidder: BIDDER_CODE, code: ERROR_CODES.MEDIA_TYPE_NOT_SUPPORTED }); } - return supported + return supported; } function hasBannerMediaType(bidRequest) { - return !!deepAccess(bidRequest, 'mediaTypes.banner') + return !!deepAccess(bidRequest, 'mediaTypes.banner'); } function hasVideoMediaType(bidRequest) { - return !!deepAccess(bidRequest, 'mediaTypes.video') + return !!deepAccess(bidRequest, 'mediaTypes.video'); } function hasValidBannerMediaType(bidRequest) { - const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); if (!sizes) { logError('AIDEM Bid Adapter: media type sizes missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); return false; } - return true + return true; } function hasValidVideoMediaType(bidRequest) { @@ -383,23 +393,38 @@ function hasValidVideoMediaType(bidRequest) { logError('AIDEM Bid Adapter: media type playerSize missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); return false; } - return true + return true; } function hasValidVideoParameters(bidRequest) { - let valid = true + let valid = true; const adUnitsParameters = deepAccess(bidRequest, 'mediaTypes.video'); const bidderParameter = deepAccess(bidRequest, 'params.video'); for (let property of REQUIRED_VIDEO_PARAMS) { - const hasAdUnitParameter = adUnitsParameters.hasOwnProperty(property) - const hasBidderParameter = bidderParameter && bidderParameter.hasOwnProperty(property) + const hasAdUnitParameter = adUnitsParameters.hasOwnProperty(property); + const hasBidderParameter = bidderParameter && bidderParameter.hasOwnProperty(property); if (!hasAdUnitParameter && !hasBidderParameter) { logError(`AIDEM Bid Adapter: ${property} is not included in either the adunit or params level`, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); - valid = false + valid = false; } } - return valid + return valid; +} + +function passesRateLimit(bidRequest) { + const rateLimit = deepAccess(bidRequest, 'params.rateLimit', 1); + if (!isNumber(rateLimit) || rateLimit > 1 || rateLimit < 0) { + logError('AIDEM Bid Adapter: invalid rateLimit (must be a number between 0 and 1)', { bidder: BIDDER_CODE, code: ERROR_CODES.INVALID_RATELIMIT }); + return false; + } + if (rateLimit !== 1) { + const randomRateValue = Math.random(); + if (randomRateValue > rateLimit) { + return false; + } + } + return true; } function hasValidParameters(bidRequest) { @@ -417,7 +442,7 @@ function hasValidParameters(bidRequest) { return false; } - return true + return true; } export const spec = { @@ -427,28 +452,32 @@ export const spec = { logInfo('bid: ', bidRequest); // check if request has valid mediaTypes - if (!hasValidMediaType(bidRequest)) return false + if (!hasValidMediaType(bidRequest)) return false; // check if request has valid media type parameters at adUnit level if (hasBannerMediaType(bidRequest) && !hasValidBannerMediaType(bidRequest)) { - return false + return false; } if (hasVideoMediaType(bidRequest) && !hasValidVideoMediaType(bidRequest)) { - return false + return false; } if (hasVideoMediaType(bidRequest) && !hasValidVideoParameters(bidRequest)) { - return false + return false; } - return hasValidParameters(bidRequest) + if (!hasValidParameters(bidRequest)) { + return false; + } + + return passesRateLimit(bidRequest); }, buildRequests: function(validBidRequests, bidderRequest) { logInfo('validBidRequests: ', validBidRequests); logInfo('bidderRequest: ', bidderRequest); - const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests) + const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests); const payloadString = JSON.stringify(prebidRequest); return { @@ -470,7 +499,7 @@ export const spec = { return; } logInfo('CPM OK'); - const bid = getPrebidResponseBidObject(bidObject) + const bid = getPrebidResponseBidObject(bidObject); bids.push(bid); }); return bids; @@ -479,14 +508,14 @@ export const spec = { onBidWon: function(bid) { // Bidder specific code logInfo('onBidWon bid: ', bid); - const notice = buildWinNotice(bid) + const notice = buildWinNotice(bid); ajax(endpoints.notice.win, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); }, onBidderError: function({ bidderRequest }) { // Bidder specific code - const notice = buildErrorNotice(bidderRequest) + const notice = buildErrorNotice(bidderRequest); ajax(endpoints.notice.error, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); }, -} +}; registerBidder(spec); diff --git a/modules/aidemBidAdapter.md b/modules/aidemBidAdapter.md index a5beca97d10..b59014c76ed 100644 --- a/modules/aidemBidAdapter.md +++ b/modules/aidemBidAdapter.md @@ -14,10 +14,12 @@ This module is GDPR and CCPA compliant, and no 3rd party userIds are allowed. ## Global Bid Params -| Name | Scope | Description | Example | Type | -|---------------|----------|---------------------|---------------|----------| -| `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | -| `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | +| Name | Scope | Description | Example | Type | +|---------------|----------|-------------------------|------------|----------| +| `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | +| `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | +| `placementId` | optional | Unique publisher tag ID | `'ABCDEF'` | `String` | +| `rateLimit` | optional | Limit the volume sent to AIDEM. Must be between 0 and 1 | `0.6` | `Number` | ### Banner Bid Params @@ -66,7 +68,8 @@ var adUnits = [{ bids: [{ bidder: 'aidem', params: { - siteId: 'prebid-test-site', + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', }, }] }]; @@ -89,7 +92,8 @@ var adUnits = [{ bids: [{ bidder: 'aidem', params: { - siteId: 'prebid-test-site', + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', }, }] }]; diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index fe5a050f436..a96b245d786 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -20,18 +20,20 @@ export const spec = { let bidIds = []; let eids; validBidRequests.forEach(bidRequest => { - let sizes = prepareSizes(bidRequest.sizes) + let formatType = getFormatType(bidRequest) + let alkimiSizes = prepareAlkimiSizes(bidRequest.sizes) if (bidRequest.userIdAsEids) { eids = eids || bidRequest.userIdAsEids } + bids.push({ token: bidRequest.params.token, pos: bidRequest.params.pos, - bidFloor: bidRequest.params.bidFloor, - width: sizes[0].width, - height: sizes[0].height, - impMediaType: getFormatType(bidRequest), + bidFloor: getBidFloor(bidRequest, formatType), + width: alkimiSizes[0].width, + height: alkimiSizes[0].height, + impMediaType: formatType, adUnitCode: bidRequest.adUnitCode }) bidIds.push(bidRequest.bidId) @@ -128,10 +130,25 @@ export const spec = { } } -function prepareSizes(sizes) { +function prepareAlkimiSizes(sizes) { return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); } +function prepareBidFloorSize(sizes) { + return sizes && sizes.length === 1 ? sizes[0] : '*'; +} + +function getBidFloor(bidRequest, formatType) { + if (typeof bidRequest.getFloor === 'function') { + const bidFloorSize = prepareBidFloorSize(bidRequest.sizes) + const floor = bidRequest.getFloor({ currency: 'USD', mediaType: formatType.toLowerCase(), size: bidFloorSize }); + if (floor && !isNaN(floor.floor) && (floor.currency === 'USD')) { + return floor.floor; + } + } + return bidRequest.params.bidFloor; +} + const getFormatType = bidRequest => { if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner' if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video' diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index d38c373ff1f..6b471ac6de2 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -159,6 +159,13 @@ export const spec = { if (bidderRequest) { consentData.gdpr = bidderRequest.gdprConsent; consentData.uspConsent = bidderRequest.uspConsent; + consentData.gppConsent = bidderRequest.gppConsent; + if (!consentData.gppConsent && bidderRequest.ortb2?.regs?.gpp) { + consentData.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } } return bids.map(bid => { @@ -373,6 +380,11 @@ export const spec = { params.us_privacy = consentData.uspConsent; } + if (consentData.gppConsent && consentData.gppConsent.gppString) { + params.gpp = consentData.gppConsent.gppString; + params.gpp_sid = consentData.gppConsent.applicableSections; + } + return params; }, parsePixelItems(pixels) { diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 77ffe0f6b94..b64334b0d77 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -304,6 +304,18 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } + if (bidderRequest?.gppConsent) { + payload.privacy = { + gpp: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest?.ortb2?.regs?.gpp) { + payload.privacy = { + gpp: bidderRequest.ortb2.regs.gpp, + gpp_sid: bidderRequest.ortb2.regs.gpp_sid + } + } + if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { // TODO: are these the correct referer values? @@ -424,8 +436,17 @@ export const spec = { } }, - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + function checkGppStatus(gppConsent) { + // this is a temporary measure to supress usersync in US-based GPP regions + // this logic will be revised when proper signals (akin to purpose1 from TCF2) can be determined for US GPP + if (gppConsent && Array.isArray(gppConsent.applicableSections)) { + return gppConsent.applicableSections.every(sec => typeof sec === 'number' && sec <= 5); + } + return true; + } + + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { return [{ type: 'iframe', url: 'https://acdn.adnxs.com/dmp/async_usersync.html' @@ -604,9 +625,8 @@ function newBid(serverBid, rtbBid, bidderRequest) { } }; - // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance if (rtbBid.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [rtbBid.adomain] }); } if (rtbBid.advertiser_id) { @@ -710,6 +730,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { displayUrl: nativeAd.displayurl, clickTrackers: nativeAd.link.click_trackers, impressionTrackers: nativeAd.impression_trackers, + video: nativeAd.video, javascriptTrackers: jsTrackers }; if (nativeAd.main_img) { diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js new file mode 100644 index 00000000000..1ad8fe27e42 --- /dev/null +++ b/modules/appushBidAdapter.js @@ -0,0 +1,188 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'appush'; +const AD_URL = 'https://hb.appush.com/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + // TODO: does the fallback make sense here? + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + 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)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + } +}; + +registerBidder(spec); diff --git a/modules/appushBidAdapter.md b/modules/appushBidAdapter.md new file mode 100644 index 00000000000..7c04c3a6425 --- /dev/null +++ b/modules/appushBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Appush Bidder Adapter +Module Type: Appush Bidder Adapter +Maintainer: support@appush.com +``` + +# Description + +Connects to Appush exchange for bids. +Appush bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'appush', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'appush', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'appush', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/arcspanRtdProvider.js b/modules/arcspanRtdProvider.js new file mode 100644 index 00000000000..a7ffa059279 --- /dev/null +++ b/modules/arcspanRtdProvider.js @@ -0,0 +1,73 @@ +import { submodule } from '../src/hook.js'; +import { mergeDeep } from '../src/utils.js'; +import {loadExternalScript} from '../src/adloader.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'arcspan'; + +/** @type {RtdSubmodule} */ +export const arcspanSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: alterBidRequests, +}; + +function init(config, userConsent) { + if (typeof config.params.silo === 'undefined') { + return false; + } + if (typeof window.arcobj2 === 'undefined') { + var scriptUrl; + if (config.params.silo === 'test') { + scriptUrl = 'https://localhost:8080/as.js'; + } else { + scriptUrl = 'https://silo' + config.params.silo + '.p7cloud.net/as.js'; + } + loadExternalScript(scriptUrl, SUBMODULE_NAME); + } + return true; +} + +function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { + var _v1 = []; + var _v1s = []; + var _v2 = []; + var arcobj1 = window.arcobj1; + if (typeof arcobj1 != 'undefined') { + if (typeof arcobj1.page_iab_codes.text != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.text); } + if (typeof arcobj1.page_iab_codes.images != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.images); } + if (typeof arcobj1.page_iab.text != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.text); } + if (typeof arcobj1.page_iab.images != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.images); } + if (typeof arcobj1.page_iab_newcodes.text != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.text])]; } + if (typeof arcobj1.page_iab_newcodes.images != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.images])]; } + + var _content = {}; + _content.data = []; + var p = {}; + p.name = 'arcspan'; + p.segment = []; + p.ext = { segtax: 6 }; + _v2.forEach(function (e) { + p.segment = p.segment.concat({ id: e }); + }); + _content.data = _content.data.concat(p); + var _ortb2 = { + site: { + name: 'arcspan', + domain: new URL(location.href).hostname, + cat: _v1, + sectioncat: _v1, + pagecat: _v1, + page: location.href, + ref: document.referrer, + keywords: _v1s.toString(), + content: _content, + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, _ortb2); + } + callback(); +} + +submodule(MODULE_NAME, arcspanSubmodule); diff --git a/modules/arcspanRtdProvider.md b/modules/arcspanRtdProvider.md new file mode 100644 index 00000000000..4aa1de02acf --- /dev/null +++ b/modules/arcspanRtdProvider.md @@ -0,0 +1,11 @@ +# Overview + +Module Name: ArcSpan Rtd Provider + +Module Type: Rtd Provider + +Maintainer: engineering@arcspan.com + +# Description + +RTD provider for ArcSpan Technologies. Contact jcabalugaz@arcspan.com for more information. diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 83b182e4ca5..39c42f193b2 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -10,11 +10,11 @@ import { parseSizesInput, tryAppendQueryString } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; -import {parseDomain} from '../src/refererDetection.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { parseDomain } from '../src/refererDetection.js'; const BIDDER_CODE = 'aso'; const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; @@ -27,6 +27,9 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + aliases: [ + {code: 'bcmint'} + ], isBidRequestValid: bid => { return !!bid.params && !!bid.params.zone; diff --git a/modules/bidViewability.js b/modules/bidViewability.js index 362401e6d1c..a5cab99b1a7 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -7,7 +7,7 @@ import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {isFn, logWarn, triggerPixel} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import adapterManager, {gdprDataHandler, uspDataHandler} from '../src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; import {find} from '../src/polyfill.js'; const MODULE_NAME = 'bidViewability'; @@ -44,6 +44,11 @@ export let fireViewabilityPixels = (globalModuleConfig, bid) => { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + // TODO - need to know what to set here for queryParams... + } + bid[BID_VURL_ARRAY].forEach(url => { // add '?' if not present in URL if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) { diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 127b534c989..ee814807331 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -125,7 +125,7 @@ export const buildBid = (bidResponse) => { requestId: deepAccess(bidResponse, 'extras.transaction_id'), width: deepAccess(bidResponse, `creative.${bid.mediaType}.width`) || 1, height: deepAccess(bidResponse, `creative.${bid.mediaType}.height`) || 1, - ttl: 3600, + ttl: 300, netRevenue: true, }); }; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index bbe0203772a..15b4175b59a 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -9,6 +9,7 @@ const URL = 'https://brightcombid.marphezis.com/hb'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], + gvlid: 883, isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js new file mode 100644 index 00000000000..b7a9aa3fdc9 --- /dev/null +++ b/modules/brightcomSSPBidAdapter.js @@ -0,0 +1,323 @@ +import { + getBidIdParameter, + isArray, + getWindowTop, + getUniqueIdentifierStr, + deepSetValue, + logError, + logWarn, + createTrackPixelHtml, + getWindowSelf, + isFn, + isPlainObject, +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'bcmssp'; +const URL = 'https://rt.marphezis.com/hb'; +const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' + +export const spec = { + code: BIDDER_CODE, + gvlid: 883, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidderError, + onTimeout, + onBidWon, + getUserSyncs, +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + const impressions = bidReqs.map(bid => { + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + + const bidFloor = _getBidFloor(bid); + + if (bidFloor) { + imp.bidfloor = bidFloor; + } + + return imp; + }) + + const referrer = bidderRequest?.refererInfo?.page || ''; + const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); + + const payload = { + id: getUniqueIdentifierStr(), + imp: impressions, + site: { + domain: bidderRequest?.refererInfo?.domain || '', + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + if (bidderRequest?.gdprConsent) { + deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest?.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa') === true) { + deepSetValue(payload, 'regs.coppa', 1); + } + + if (bidReqs?.[0]?.schain) { + deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) + } + + if (bidReqs?.[0]?.userIdAsEids) { + deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) + } + + if (bidReqs?.[0].userId) { + deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) + } + + return { + method: 'POST', + url: URL, + data: JSON.stringify(payload), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + let response = []; + if (!serverResponse.body || typeof serverResponse.body != 'object') { + logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return response; + } + + const {body: {id, seatbid}} = serverResponse; + + try { + if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { + response = seatbid[0].bid.map(bid => { + return { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(bid), + ttl: 60, + meta: { + advertiserDomains: bid?.adomain || [] + } + }; + }); + } + } catch (e) { + logError(e, {id, seatbid}); + } + + return response; +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function onTimeout(timeoutData) { + if (timeoutData === null) { + return; + } + + _trackEvent('timeout', timeoutData); +} + +function onBidderError(errorData) { + if (errorData === null || !errorData.bidderRequest) { + return; + } + + _trackEvent('error', errorData.bidderRequest) +} + +function onBidWon(bid) { + if (bid === null) { + return; + } + + _trackEvent('bidwon', bid) +} + +function _trackEvent(endpoint, data) { + ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { + method: 'POST', + withCredentials: false + }); +} + +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 _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, {w, h} = {}) { + return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, {w, h}) : 0; +} + +function _isIframe() { + try { + return getWindowSelf() !== getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, {w, h} = {}) { + let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return {width, height, left, top, right, bottom}; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, {w, h} = {}) { + const elementBoundingBox = _getBoundingBox(element, {w, h}); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([{ + left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight + }, elementBoundingBox]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', mediaType: '*', size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +registerBidder(spec); diff --git a/modules/brightcomSSPBidAdapter.md b/modules/brightcomSSPBidAdapter.md new file mode 100644 index 00000000000..8d0e4ec70dc --- /dev/null +++ b/modules/brightcomSSPBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: Brightcom SSP Bid Adapter +Module Type: Bidder Adapter +Maintainer: alexandruc@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 7c6b0411023..28f3fe3a166 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -5,6 +5,7 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'ccx' const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' +const GVLID = 773; const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv'] const SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4] @@ -19,8 +20,7 @@ function _getDeviceObj () { function _getSiteObj (bidderRequest) { let site = {} - // TODO: does the fallback to window.location make sense? - let url = bidderRequest?.refererInfo?.page || window.location.href + let url = bidderRequest?.refererInfo?.page || '' if (url.length > 0) { url = url.split('?')[0] } @@ -140,6 +140,7 @@ function _buildResponse (bid, currency, ttl) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 75e73ffda89..082fb0ac4db 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -87,6 +87,11 @@ export const spec = { logMessage(e); } + const firstPartyData = bidderRequest.ortb2 || {}; + const userObj = firstPartyData.user; + const siteObj = firstPartyData.site; + const appObj = firstPartyData.app; + // TODO: does the fallback to window.location make sense? const location = refferLocation || winLocation; let placements = []; @@ -97,6 +102,9 @@ export const spec = { secure: location.protocol === 'https:' ? 1 : 0, host: location.host, page: location.pathname, + userObj, + siteObj, + appObj, placements: placements }; diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js new file mode 100644 index 00000000000..2c13ad87d75 --- /dev/null +++ b/modules/confiantRtdProvider.js @@ -0,0 +1,128 @@ +/** + * This module provides comprehensive detection of security, quality, and privacy threats by Confiant Inc, + * the industry leader in real-time detecting and blocking of bad ads + * + * The {@link module:modules/realTimeData} module is required + * The module will inject a Confiant Inc. script into the page to monitor ad impressions + * @module modules/confiantRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { logError, generateUUID } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +/** + * Injects the Confiant Inc. configuration script into the page, based on proprtyId provided + * @param {string} propertyId + */ +function injectConfigScript(propertyId) { + const scriptSrc = `https://cdn.confiant-integrations.net/${propertyId}/gpt_and_prebid/config.js`; + + loadExternalScript(scriptSrc, 'confiant', () => {}); +} + +/** + * Set up page with Confiant integration + * @param {Object} config + */ +function setupPage(config) { + const propertyId = config?.params?.propertyId; + if (!propertyId) { + logError('Confiant pbjs module: no propertyId provided'); + return false; + } + + const confiant = window.confiant || Object.create(null); + confiant[propertyId] = confiant[propertyId] || Object.create(null); + confiant[propertyId].clientSettings = confiant[propertyId].clientSettings || Object.create(null); + confiant[propertyId].clientSettings.isMGBL = true; + confiant[propertyId].clientSettings.prebidExcludeBidders = config?.params?.prebidExcludeBidders; + confiant[propertyId].clientSettings.prebidNameSpace = config?.params?.prebidNameSpace; + + if (config?.params?.shouldEmitBillableEvent) { + if (window.frames['cnftComm']) { + subscribeToConfiantCommFrame(window); + } else { + setUpMutationObserver(); + } + } + + injectConfigScript(propertyId); + return true; +} + +/** + * Subscribe to window's message events to report Billable events + * @param {Window} targetWindow window instance to subscribe to + */ +function subscribeToConfiantCommFrame(targetWindow) { + targetWindow.addEventListener('message', reportBillableEvents); +} + +let mutationObserver; +/** + * Set up mutation observer to subscribe to Confiant's communication channel ASAP + */ +function setUpMutationObserver() { + mutationObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((addedNode) => { + if (addedNode.nodeName === 'IFRAME' && addedNode.name === 'cnftComm' && !addedNode.pbjsModuleSubscribed) { + addedNode.pbjsModuleSubscribed = true; + mutationObserver.disconnect(); + mutationObserver = null; + const iframeWindow = addedNode.contentWindow; + subscribeToConfiantCommFrame(iframeWindow); + } + }); + }); + }); + mutationObserver.observe(document.head, { childList: true, subtree: true }); +} + +/** + * Emit billable event when Confiant integration reports that it has monitored an impression + */ +function reportBillableEvents (e) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + auctionId: e.data.auctionId, + billingId: generateUUID(), + transactionId: e.data.transactionId, + type: 'impression', + vendor: 'confiant' + }); +} + +/** + * Confiant submodule registration + */ +function registerConfiantSubmodule() { + submodule('realTimeData', { + name: 'confiant', + init: (config) => { + try { + return setupPage(config); + } catch (err) { + logError(err.message); + if (mutationObserver) { + mutationObserver.disconnect(); + } + return false; + } + } + }); +} + +registerConfiantSubmodule(); + +export default { + injectConfigScript, + setupPage, + subscribeToConfiantCommFrame, + setUpMutationObserver, + reportBillableEvents, + registerConfiantSubmodule +}; diff --git a/modules/confiantRtdProvider.md b/modules/confiantRtdProvider.md new file mode 100644 index 00000000000..e92c0aabcba --- /dev/null +++ b/modules/confiantRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Confiant Inc. Rtd provider +Module Type: Rtd Provider +Maintainer: +``` + +Confiant’s module provides comprehensive detection of security, quality, and privacy threats across your ad stack. +Confiant is the industry leader in real-time detecting and blocking of bad ads when it comes to protecting your users and brand reputation. + +To start using this module, please contact [Confiant](https://www.confiant.com/contact) to get an account and customer key. + + +# Integration + +1) Build Prebid bundle with Confiant module included: + + +``` +gulp build --modules=confiantRtdProvider,... +``` + +2) Include the resulting bundle on your page. + +# Configuration + +Configuration of Confiant module is plain simple: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'confiant', + params: { + // so please get in touch with us so we could help you to set up the module with proper parameters + propertyId: '', // required, string param, obtained from Confiant Inc. + prebidExcludeBidders: '', // optional, comma separated list of bidders to exclude from Confiant's prebid.js integration + prebidNameSpace: '', // optional, string param, namespace for prebid.js integration + shouldEmitBillableEvent: false, // optional, boolean param, upon being set to true enables firing of the BillableEvent upon Confiant's impression scanning + } + }] + } +}); +``` diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 24ef8ad0af5..38c9c00b0ed 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -66,6 +66,11 @@ export const connectIdSubmodule = { us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' }; + if (connectIdSubmodule.isUnderGPPJurisdiction(consentData)) { + data.gpp = consentData.gppConsent.gppString; + data.gpp_sid = encodeURIComponent(consentData.gppConsent.applicableSections.join(',')); + } + INPUT_PARAM_KEYS.forEach(key => { if (typeof params[key] != 'undefined') { data[key] = params[key]; @@ -98,7 +103,7 @@ export const connectIdSubmodule = { }, /** - * Utility function that returns a boolean flag indicating if the opporunity + * Utility function that returns a boolean flag indicating if the opportunity * is subject to GDPR * @returns {Boolean} */ @@ -106,6 +111,15 @@ export const connectIdSubmodule = { return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); }, + /** + * Utility function that returns a boolean flag indicating if the opportunity + * is subject to GPP jurisdiction. + * @returns {Boolean} + */ + isUnderGPPJurisdiction(consentData) { + return !!(consentData && consentData.gppConsent && consentData.gppConsent.gppString); + }, + /** * Utility function that returns a boolean flag indicating if the user * has opeted out via the Yahoo easy-opt-out mechanism. diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 6ca12010c74..bdf6649bcba 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -18,12 +18,16 @@ const CMP_VERSION = 2; export let userCMP; export let consentTimeout; +export let actionTimeout; export let gdprScope; export let staticConsentData; let consentData; let addedConsentHook = false; let provisionalConsent; +let onTimeout; +let timer = null; +let actionTimer = null; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { @@ -39,6 +43,12 @@ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) } +export function setActionTimeout(timeout = setTimeout) { + clearTimeout(timer); + timer = null; + actionTimer = timeout(onTimeout, actionTimeout); +} + /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function @@ -84,6 +94,7 @@ function lookupIabConsent({onSuccess, onError}) { processCmpData(tcfData, {onSuccess, onError}); } else { provisionalConsent = tcfData; + if (!isNaN(actionTimeout) && actionTimer === null && timer != null) setActionTimeout(); } } else { onError('CMP unable to register callback function. Please check CMP setup.'); @@ -94,7 +105,7 @@ function lookupIabConsent({onSuccess, onError}) { const { cmpFrame, cmpFunction } = findCMP(); if (!cmpFrame) { - return onError('CMP not found.'); + return onError('TCF2 CMP not found.'); } // to collect the consent information from the user, we perform two calls to the CMP in parallel: // first to collect the user's consent choices represented in an encoded string (via getConsentData) @@ -162,14 +173,20 @@ function lookupIabConsent({onSuccess, onError}) { * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra * error arguments that will be undefined if there's no error. */ -function loadConsentData(cb) { +export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) { let isDone = false; - let timer = null; function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { if (timer != null) { clearTimeout(timer); + timer = null; + } + + if (actionTimer != null) { + clearTimeout(actionTimer); + actionTimer = null; } + isDone = true; gdprDataHandler.setConsentData(consentData); if (typeof cb === 'function') { @@ -188,10 +205,11 @@ function loadConsentData(cb) { done(null, true, msg, ...extraArgs); } } - cmpCallMap[userCMP](callbacks); + + callMap[userCMP](callbacks); if (!isDone) { - const onTimeout = () => { + onTimeout = () => { const continueToAuction = (data) => { done(data, false, 'CMP did not load, continuing auction...'); } @@ -200,10 +218,16 @@ function loadConsentData(cb) { onError: () => continueToAuction(storeConsentData(undefined)) }) } + if (consentTimeout === 0) { onTimeout(); } else { - timer = setTimeout(onTimeout, consentTimeout); + if (timer != null) { + clearTimeout(timer); + timer = null; + } + + timer = timeout(onTimeout, consentTimeout); } } } @@ -314,11 +338,11 @@ export function resetConsentData() { * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ export function setConsentConfig(config) { - // if `config.gdpr` or `config.usp` exist, assume new config format. + // if `config.gdpr`, `config.usp` or `config.gpp` exist, assume new config format. // else for backward compatability, just use `config` - config = config && (config.gdpr || config.usp ? config.gdpr : config); + config = config && (config.gdpr || config.usp || config.gpp ? config.gdpr : config); if (!config || typeof config !== 'object') { - logWarn('consentManagement config not defined, exiting consent manager'); + logWarn('consentManagement (gdpr) config not defined, exiting consent manager'); return; } if (isStr(config.cmpApi)) { @@ -328,6 +352,10 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); } + if (isNumber(config.actionTimeout)) { + actionTimeout = config.actionTimeout; + } + if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js new file mode 100644 index 00000000000..8a9c3f999f0 --- /dev/null +++ b/modules/consentManagementGpp.js @@ -0,0 +1,386 @@ +/** + * This module adds GPP consentManagement support to prebid.js. It interacts with + * supported CMPs (Consent Management Platforms) to grab the user's consent information + * and make it available for any GPP supported adapters to read/pass this information to + * their system and for various other features/modules in Prebid.js. + */ +import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {gppDataHandler} from '../src/adapterManager.js'; +import {includes} from '../src/polyfill.js'; +import {timedAuctionHook} from '../src/utils/perfMetrics.js'; +import { enrichFPD } from '../src/fpd/enrichment.js'; + +const DEFAULT_CMP = 'iab'; +const DEFAULT_CONSENT_TIMEOUT = 10000; +const CMP_VERSION = 1; + +export let userCMP; +export let consentTimeout; +export let staticConsentData; + +let consentData; +let addedConsentHook = false; + +// add new CMPs here, with their dedicated lookup function +const cmpCallMap = { + 'iab': lookupIabConsent, + 'static': lookupStaticConsentData +}; + +/** + * This function checks the state of the IAB gppData's applicableSection field (to ensure it's populated and has a valid value). + * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. + * + * TODO --- The initial version of the GPP CMP API spec used this naming convention, but it was later changed as an update to the spec. + * CMPs should adjust their logic to use the new format (applicableSecctions), but that may not be the case with the initial release. + * Added support just in case for this transition period, can likely be removed at a later date... + * @param gppData represents the IAB gppData object + * @returns true|false + */ +function checkApplicableSectionIsReady(gppData) { + return gppData && Array.isArray(gppData.applicableSection) && gppData.applicableSection.length > 0 && gppData.applicableSection[0] !== 0; +} + +/** + * This function checks the state of the IAB gppData's applicableSections field (to ensure it's populated and has a valid value). + * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. + * @param gppData represents the IAB gppData object + * @returns true|false + */ +function checkApplicableSectionsIsReady(gppData) { + return gppData && Array.isArray(gppData.applicableSections) && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== 0; +} + +/** + * This function reads the consent string from the config to obtain the consent information of the user. + * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP + */ +function lookupStaticConsentData({onSuccess, onError}) { + processCmpData(staticConsentData, {onSuccess, onError}); +} + +/** + * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. + * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function + * based on the appropriate result. + * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP + * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + */ +function lookupIabConsent({onSuccess, onError}) { + const cmpApiName = '__gpp'; + const cmpCallbacks = {}; + let registeredPostMessageResponseListener = false; + + function findCMP() { + let f = window; + let cmpFrame; + let cmpDirectAccess = false; + while (true) { + try { + if (typeof f[cmpApiName] === 'function') { + cmpFrame = f; + cmpDirectAccess = true; + break; + } + } catch (e) {} + + // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env + try { + if (f.frames['__gppLocator']) { + cmpFrame = f; + break; + } + } catch (e) {} + + if (f === window.top) break; + f = f.parent; + } + + return { + cmpFrame, + cmpDirectAccess + }; + } + + const {cmpFrame, cmpDirectAccess} = findCMP(); + + if (!cmpFrame) { + return onError('GPP CMP not found.'); + } + + const invokeCMP = (cmpDirectAccess) ? invokeCMPDirect : invokeCMPFrame; + + function invokeCMPDirect({command, callback, parameter, version = CMP_VERSION}, resultCb) { + if (typeof resultCb === 'function') { + resultCb(cmpFrame[cmpApiName](command, callback, parameter, version)); + } else { + cmpFrame[cmpApiName](command, callback, parameter, version); + } + } + + function invokeCMPFrame({command, callback, parameter, version = CMP_VERSION}, resultCb) { + const callName = `${cmpApiName}Call`; + if (!registeredPostMessageResponseListener) { + // when we get the return message, call the stashed callback; + window.addEventListener('message', readPostMessageResponse, false); + registeredPostMessageResponseListener = true; + } + + // call CMP via postMessage + const callId = Math.random().toString(); + const msg = { + [callName]: { + command: command, + parameter, + version, + callId: callId + } + }; + + // TODO? - add logic to check if random was already used in the same session, and roll another if so? + cmpCallbacks[callId] = (typeof callback === 'function') ? callback : resultCb; + cmpFrame.postMessage(msg, '*'); + + function readPostMessageResponse(event) { + const cmpDataPkgName = `${cmpApiName}Return`; + const json = (typeof event.data === 'string' && event.data.includes(cmpDataPkgName)) ? JSON.parse(event.data) : event.data; + if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { + const payload = json[cmpDataPkgName]; + + if (cmpCallbacks.hasOwnProperty(payload.callId)) { + cmpCallbacks[payload.callId](payload.returnValue); + } + } + } + } + + const startupMsg = (cmpDirectAccess) ? 'Detected GPP CMP API is directly accessible, calling it now...' + : 'Detected GPP CMP is outside the current iframe where Prebid.js is located, calling it now...'; + logInfo(startupMsg); + + invokeCMP({ + command: 'addEventListener', + callback: function (evt) { + if (evt) { + logInfo(`Received a ${(cmpDirectAccess ? 'direct' : 'postmsg')} response from GPP CMP for event`, evt); + if (evt.eventName === 'sectionChange' || evt.pingData.cmpStatus === 'loaded') { + invokeCMP({command: 'getGPPData'}, function (gppData) { + logInfo(`Received a ${cmpDirectAccess ? 'direct' : 'postmsg'} response from GPP CMP for getGPPData`, gppData); + processCmpData(gppData, {onSuccess, onError}); + }); + } else if (evt.pingData.cmpStatus === 'error') { + onError('CMP returned with a cmpStatus:error response. Please check CMP setup.'); + } + } + } + }); +} + +/** + * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler. + * + * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra + * error arguments that will be undefined if there's no error. + */ +function loadConsentData(cb) { + let isDone = false; + let timer = null; + + function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + if (timer != null) { + clearTimeout(timer); + } + isDone = true; + gppDataHandler.setConsentData(consentData); + if (typeof cb === 'function') { + cb(shouldCancelAuction, errMsg, ...extraArgs); + } + } + + if (!includes(Object.keys(cmpCallMap), userCMP)) { + done(null, false, `GPP CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + return; + } + + const callbacks = { + onSuccess: (data) => done(data, false), + onError: function (msg, ...extraArgs) { + done(null, true, msg, ...extraArgs); + } + } + cmpCallMap[userCMP](callbacks); + + if (!isDone) { + const onTimeout = () => { + const continueToAuction = (data) => { + done(data, false, 'GPP CMP did not load, continuing auction...'); + } + processCmpData(consentData, { + onSuccess: continueToAuction, + onError: () => continueToAuction(storeConsentData(undefined)) + }) + } + if (consentTimeout === 0) { + onTimeout(); + } else { + timer = setTimeout(onTimeout, consentTimeout); + } + } +} + +/** + * Like `loadConsentData`, but cache and re-use previously loaded data. + * @param cb + */ +function loadIfMissing(cb) { + if (consentData) { + logInfo('User consent information already known. Pulling internally stored information...'); + // eslint-disable-next-line standard/no-callback-literal + cb(false); + } else { + loadConsentData(cb); + } +} + +/** + * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the + * user's encoded consent string from the supported CMP. Once obtained, the module will store this + * data as part of a gppConsent object which gets transferred to adapterManager's gppDataHandler object. + * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. + * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export const requestBidsHook = timedAuctionHook('gpp', function requestBidsHook(fn, reqBidsConfigObj) { + loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { + if (errMsg) { + let log = logWarn; + if (shouldCancelAuction) { + log = logError; + errMsg = `${errMsg} Canceling auction as per consentManagement config.`; + } + log(errMsg, ...extraArgs); + } + + if (shouldCancelAuction) { + fn.stopTiming(); + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); + } else { + logError('Error executing bidsBackHandler'); + } + } else { + fn.call(this, reqBidsConfigObj); + } + }); +}); + +/** + * This function checks the consent data provided by CMP to ensure it's in an expected state. + * If it's bad, we call `onError` + * If it's good, then we store the value and call `onSuccess` + */ +function processCmpData(consentObject, {onSuccess, onError}) { + function checkData() { + const gppString = consentObject && consentObject.gppString; + const gppSection = (checkApplicableSectionsIsReady(consentObject)) ? consentObject.applicableSections + : (checkApplicableSectionIsReady(consentObject)) ? consentObject.applicableSection : []; + + return !!( + (!Array.isArray(gppSection)) || + (Array.isArray(gppSection) && (!gppString || !isStr(gppString))) + ); + } + + if (checkData()) { + onError(`CMP returned unexpected value during lookup process.`, consentObject); + } else { + onSuccess(storeConsentData(consentObject)); + } +} + +/** + * Stores CMP data locally in module 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) { + consentData = { + gppString: (cmpConsentObject) ? cmpConsentObject.gppString : undefined, + + fullGppData: (cmpConsentObject) || undefined, + }; + consentData.applicableSections = (checkApplicableSectionsIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSections + : (checkApplicableSectionIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSection : []; + consentData.apiVersion = CMP_VERSION; + return consentData; +} + +/** + * Simply resets the module's consentData variable back to undefined, mainly for testing purposes + */ +export function resetConsentData() { + consentData = undefined; + userCMP = undefined; + consentTimeout = undefined; + gppDataHandler.reset(); +} + +/** + * A configuration function that initializes some module variables, as well as add a hook into the requestBids function + * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) + */ +export function setConsentConfig(config) { + config = config && config.gpp; + if (!config || typeof config !== 'object') { + logWarn('consentManagement.gpp config not defined, exiting consent manager module'); + return; + } + + if (isStr(config.cmpApi)) { + userCMP = config.cmpApi; + } else { + userCMP = DEFAULT_CMP; + logInfo(`consentManagement.gpp config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); + } + + if (isNumber(config.timeout)) { + consentTimeout = config.timeout; + } else { + consentTimeout = DEFAULT_CONSENT_TIMEOUT; + logInfo(`consentManagement.gpp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); + } + + if (userCMP === 'static') { + if (isPlainObject(config.consentData)) { + staticConsentData = config.consentData; + consentTimeout = 0; + } else { + logError(`consentManagement.gpp config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); + } + } + + logInfo('consentManagement.gpp module has been activated...'); + + if (!addedConsentHook) { + $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + } + addedConsentHook = true; + gppDataHandler.enable(); + loadConsentData(); // immediately look up consent data to make it available without requiring an auction +} +config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); + +export function enrichFPDHook(next, fpd) { + return next(fpd.then(ortb2 => { + const consent = gppDataHandler.getConsentData(); + if (consent) { + if (Array.isArray(consent.applicableSections)) { + deepSetValue(ortb2, 'regs.gpp_sid', consent.applicableSections); + } + deepSetValue(ortb2, 'regs.gpp', consent.gppString); + } + return ortb2; + })); +} + +enrichFPD.before(enrichFPDHook); diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 0a99ec4a913..fcc13152aa9 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -100,6 +100,14 @@ function lookupUspConsent({onSuccess, onError}) { return onError('USP CMP not found.'); } + function registerDataDelHandler(invoker, arg2) { + try { + invoker('registerDeletion', arg2, adapterManager.callDataDeletionRequest); + } catch (e) { + logError('Error invoking CMP `registerDeletion`:', e); + } + } + // to collect the consent information from the user, we perform a call to USPAPI // to collect the user's consent choices represented as a string (via getUSPData) @@ -115,11 +123,7 @@ function lookupUspConsent({onSuccess, onError}) { USPAPI_VERSION, callbackHandler.consentDataCallback ); - uspapiFunction( - 'registerDeletion', - USPAPI_VERSION, - adapterManager.callDataDeletionRequest - ) + registerDataDelHandler(uspapiFunction, USPAPI_VERSION); } else { logInfo( 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' @@ -129,11 +133,7 @@ function lookupUspConsent({onSuccess, onError}) { uspapiFrame, callbackHandler.consentDataCallback ); - callUspApiWhileInIframe( - 'registerDeletion', - uspapiFrame, - adapterManager.callDataDeletionRequest - ); + registerDataDelHandler(callUspApiWhileInIframe, uspapiFrame); } let listening = false; diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index c91f1a7f906..4e2a92fb594 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -180,12 +180,26 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncUrl = 'https://sync.serverbid.com/ss/' + siteId + '.html'; + if (syncOptions.iframeEnabled) { + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl = appendUrlParam(syncUrl, `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); + } else { + syncUrl = appendUrlParam(syncUrl, `gdpr=0&gdpr_consent=${gdprConsent.consentString}`); + } + } + + if (uspConsent && uspConsent.consentString) { + syncUrl = appendUrlParam(syncUrl, `us_privacy=${uspConsent.consentString}`); + } + if (!serverResponses || serverResponses.length === 0 || !serverResponses[0].body.bdr || serverResponses[0].body.bdr !== 'cx') { return [{ type: 'iframe', - url: 'https://sync.serverbid.com/ss/' + siteId + '.html' + url: syncUrl }]; } } @@ -294,4 +308,8 @@ function getBidFloor(bid, sizes) { return floor; } +function appendUrlParam(url, queryString) { + return `${url}${url.indexOf('?') > -1 ? '&' : '?'}${queryString}`; +} + registerBidder(spec); diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 5567103db69..fb42066a008 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -27,7 +27,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 132; +export const FAST_BID_VERSION_CURRENT = 134; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index a11899609bc..604d7235d0f 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -1,307 +1,234 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {OUTSTREAM} from '../src/video.js'; -import { - deepAccess, - generateUUID, - getBidIdParameter, - getParameterByName, - getValue, - isArray, - isNumber, - isStr, - logError, - logWarn, - parseSizesInput, -} from '../src/utils.js'; -import {Renderer} from '../src/Renderer.js'; -import {find} from '../src/polyfill.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {generateUUID, getParameterByName, isNumber, logError, logInfo} from '../src/utils.js'; // ------------------------------------ const BIDDER_CODE = 'cwire'; -export const ENDPOINT_URL = 'https://embed.cwi.re/delivery/prebid'; -export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer.min.js'; -// ------------------------------------ -export const CW_PAGE_VIEW_ID = generateUUID(); -const LS_CWID_KEY = 'cw_cwid'; -const CW_GROUPS_QUERY = 'cwgroups'; -const CW_CREATIVE_QUERY = 'cwcreative'; +const CWID_KEY = 'cw_cwid'; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const BID_ENDPOINT = 'https://prebid.cwi.re/v1/bid'; +export const EVENT_ENDPOINT = 'https://prebid.cwi.re/v1/event'; /** - * ------------------------------------ - * ------------------------------------ - * @param bid - * @returns {Array} + * Allows limiting ad impressions per site render. Unique per prebid instance ID. */ -export function getSlotSizes(bid) { - return parseSizesInput(getAllMediaSizes(bid)); -} +export const pageViewId = generateUUID(); + +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** - * ------------------------------------ - * ------------------------------------ + * Retrieve dimensions and CSS max height/width from a given slot and attach the properties to the bidRequest. * @param bid - * @returns {*[]} + * @returns {*&{cwExt: {dimensions: {width: number, height: number}, style: {maxWidth: number, maxHeight: number}}}} */ -export function getAllMediaSizes(bid) { - let playerSizes = deepAccess(bid, 'mediaTypes.video.playerSize'); - let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); - let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - - const sizes = []; - - if (isArray(playerSizes)) { - playerSizes.forEach((s) => { - sizes.push(s); - }) - } - - if (isArray(videoSizes)) { - videoSizes.forEach((s) => { - sizes.push(s); - }) +function slotDimensions(bid) { + let adUnitCode = bid.adUnitCode; + let slotEl = document.getElementById(adUnitCode); + + if (slotEl) { + logInfo(`Slot element found: ${adUnitCode}`) + const slotW = slotEl.offsetWidth + const slotH = slotEl.offsetHeight + const cssMaxW = slotEl.style?.maxWidth; + const cssMaxH = slotEl.style?.maxHeight; + logInfo(`Slot dimensions (w/h): ${slotW} / ${slotH}`) + logInfo(`Slot Styles (maxW/maxH): ${cssMaxW} / ${cssMaxH}`) + + bid = { + ...bid, + cwExt: { + dimensions: { + width: slotW, + height: slotH, + }, + style: { + ...(cssMaxW) && { + maxWidth: cssMaxW + }, + ...(cssMaxH) && { + maxHeight: cssMaxH + } + } + } + } } + return bid +} - if (isArray(bannerSizes)) { - bannerSizes.forEach((s) => { - sizes.push(s); - }) +/** + * Extracts feature flags from a comma-separated url parameter `cwfeatures`. + * + * @returns *[] + */ +function getFeatureFlags() { + let ffParam = getParameterByName('cwfeatures') + if (ffParam) { + return ffParam.split(',') } - return sizes; + return [] } -const getQueryVariable = (variable) => { - let value = getParameterByName(variable); - if (value === '') { - value = null; +function getRefGroups() { + const groups = getParameterByName('cwgroups') + if (groups) { + return groups.split(',') } - return value; -}; + return [] +} /** - * ------------------------------------ - * ------------------------------------ - * @param validBidRequests - * @returns {*[]} + * Reads the CWID from local storage. */ -export const mapSlotsData = function(validBidRequests) { - const slots = []; - validBidRequests.forEach(bid => { - const bidObj = {}; - // get testing / debug params - let cwcreative = getValue(bid.params, 'cwcreative'); - let refgroups = getValue(bid.params, 'refgroups'); - let cwapikey = getValue(bid.params, 'cwapikey'); +function getCwid() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(CWID_KEY) : null; +} - // get the pacement and page ids - let placementId = getValue(bid.params, 'placementId'); - let pageId = getValue(bid.params, 'pageId'); - // get the rest of the auction/bid/transaction info - bidObj.auctionId = getBidIdParameter('auctionId', bid); - bidObj.adUnitCode = getBidIdParameter('adUnitCode', bid); - bidObj.bidId = getBidIdParameter('bidId', bid); - bidObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); - bidObj.placementId = placementId; - bidObj.pageId = pageId; - bidObj.mediaTypes = getBidIdParameter('mediaTypes', bid); - bidObj.transactionId = getBidIdParameter('transactionId', bid); - bidObj.sizes = getSlotSizes(bid); - bidObj.cwcreative = cwcreative; - bidObj.refgroups = refgroups; - bidObj.cwapikey = cwapikey; - slots.push(bidObj); - }); +function hasCwid() { + return storage.localStorageIsEnabled() && storage.getDataFromLocalStorage(CWID_KEY); +} - return slots; -}; +/** + * Store the CWID to local storage. + */ +function updateCwid(cwid) { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(CWID_KEY, cwid) + } else { + logInfo(`Could not set CWID ${cwid} in localstorage`); + } +} + +/** + * Extract and collect cwire specific extensions. + */ +function getCwExtension() { + const cwId = getCwid(); + const cwCreative = getParameterByName('cwcreative') + const cwGroups = getRefGroups() + const cwFeatures = getFeatureFlags(); + // Enable debug flag by passing ?cwdebug=true as url parameter. + // Note: pbjs_debug=true enables it on prebid level + // More info: https://docs.prebid.org/troubleshooting/troubleshooting-guide.html#turn-on-prebidjs-debug-messages + const debug = getParameterByName('cwdebug'); + + return { + ...(cwId) && { + cwid: cwId + }, + ...(cwGroups.length > 0) && { + refgroups: cwGroups + }, + ...(cwFeatures.length > 0) && { + featureFlags: cwFeatures + }, + ...(cwCreative) && { + cwcreative: cwCreative + }, + ...(debug) && { + debug: true + } + }; +} export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER], + /** - * Determines whether or not the given bid request is valid. + * Determines whether 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) { - bid.params = bid.params || {}; - - if (bid.params.cwcreative && !isStr(bid.params.cwcreative)) { - logError('cwcreative must be of type string!'); - return false; - } - - if (!bid.params.placementId || !isNumber(bid.params.placementId)) { - logError('placementId not provided or invalid'); + isBidRequestValid: function (bid) { + if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { + logError('placementId not provided or not a number'); return false; } - if (!bid.params.pageId || !isNumber(bid.params.pageId)) { - logError('pageId not provided'); + if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { + logError('pageId not provided or not a number'); return false; } - return true; }, /** - * ------------------------------------ - * itterate trough slots array and try - * to extract first occurence of a given - * key, if not found - return null - * ------------------------------------ - */ - getFirstValueOrNull: function(slots, key) { - const found = slots.find((item) => { - return (typeof item[key] !== 'undefined'); - }); - - return (found) ? found[key] : null; - }, - - /** - * ------------------------------------ - * Make a server request from the - * list of BidRequests. - * ------------------------------------ - * @param {validBidRequests[]} - an array of bids + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} validBidRequests An array of bids. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests, bidderRequest) { - let slots = []; - let referer; - try { - referer = bidderRequest?.refererInfo?.page; - slots = mapSlotsData(validBidRequests); - } catch (e) { - logWarn(e); - } + buildRequests: function (validBidRequests, bidderRequest) { + // There are more fields on the refererInfo object + let referrer = bidderRequest?.refererInfo?.page - let refgroups = []; - - const cwCreative = getQueryVariable(CW_CREATIVE_QUERY) || null; - const cwCreativeIdFromConfig = this.getFirstValueOrNull(slots, 'cwcreative'); - const refGroupsFromConfig = this.getFirstValueOrNull(slots, 'refgroups'); - const cwApiKeyFromConfig = this.getFirstValueOrNull(slots, 'cwapikey'); - const rgQuery = getQueryVariable(CW_GROUPS_QUERY); - - if (refGroupsFromConfig !== null) { - refgroups = refGroupsFromConfig.split(','); - } - - if (rgQuery !== null) { - // override if query param is present - refgroups = []; - refgroups = rgQuery.split(','); - } - - const localStorageCWID = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(LS_CWID_KEY) : null; + // process bid requests + let processed = validBidRequests + .map(bid => slotDimensions(bid)) + // Flattens the pageId and placement Id for backwards compatibility. + .map((bid) => ({...bid, pageId: bid.params?.pageId, placementId: bid.params?.placementId})); + const extensions = getCwExtension(); const payload = { - cwid: localStorageCWID, - refgroups, - cwcreative: cwCreative || cwCreativeIdFromConfig, - slots: slots, - cwapikey: cwApiKeyFromConfig, - httpRef: referer || '', - pageViewId: CW_PAGE_VIEW_ID, + slots: processed, + httpRef: referrer, + // TODO: Verify whether the auctionId and the usage of pageViewId make sense. + pageViewId: pageViewId, + sdk: { + version: '$prebid.version$' + }, + ...extensions }; - + const payloadString = JSON.stringify(payload); return { method: 'POST', - url: ENDPOINT_URL, - data: payload + url: BID_ENDPOINT, + data: payloadString, }; }, - /** * 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 bidResponses = []; - - try { - if (typeof bidRequest.data === 'string') { - bidRequest.data = JSON.parse(bidRequest.data); + interpretResponse: function (serverResponse, bidRequest) { + if (!hasCwid()) { + const cwid = serverResponse.body?.cwid + if (cwid) { + updateCwid(cwid); } - const serverBody = serverResponse.body; - serverBody.bids.forEach((br) => { - const bidReq = find(bidRequest.data.slots, bid => bid.bidId === br.requestId); - - let mediaType = BANNER; - - const bidResponse = { - requestId: br.requestId, - cpm: br.cpm, - bidderCode: BIDDER_CODE, - width: br.dimensions[0], - height: br.dimensions[1], - creativeId: br.creativeId, - currency: br.currency, - netRevenue: br.netRevenue, - ttl: br.ttl, - meta: { - advertiserDomains: br.adomains ? br.advertiserDomains : [], - }, - - }; - - // ------------------------------------ - // IF BANNER - // ------------------------------------ - - if (deepAccess(bidReq, 'mediaTypes.banner')) { - bidResponse.ad = br.html; - } - // ------------------------------------ - // IF VIDEO - // ------------------------------------ - if (deepAccess(bidReq, 'mediaTypes.video')) { - mediaType = VIDEO; - bidResponse.vastXml = br.vastXml; - bidResponse.videoScript = br.html; - const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); - if (mediaTypeContext === OUTSTREAM) { - const r = Renderer.install({ - id: bidResponse.requestId, - adUnitCode: bidReq.adUnitCode, - url: RENDERER_URL, - loaded: false, - config: { - ...deepAccess(bidReq, 'mediaTypes.video'), - ...deepAccess(br, 'outstream', {}) - } - }); + } - // set renderer - try { - bidResponse.renderer = r; - bidResponse.renderer.setRender(function(bid) { - if (window.CWIRE && window.CWIRE.outstream) { - window.CWIRE.outstream.renderAd(bid); - } - }); - } catch (err) { - logWarn('Prebid Error calling setRender on newRenderer', err); - } - } - } + // Rename `html` response property to `ad` as used by prebid. + const bids = serverResponse.body?.bids.map(({html, ...rest}) => ({...rest, ad: html})); + return bids || []; + }, - bidResponse.mediaType = mediaType; - bidResponses.push(bidResponse); - }); - } catch (e) { - logWarn(e); + onBidWon: function (bid) { + logInfo(`Bid won.`) + const event = { + type: 'BID_WON', + payload: { + bid: bid + } } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + }, - return bidResponses; + onBidderError: function (error, bidderRequest) { + logInfo(`Bidder error: ${error}`) + const event = { + type: 'BID_ERROR', + payload: { + error: error, + bidderRequest: bidderRequest + } + } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, + }; registerBidder(spec); diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index 188894cd202..026c2943c6b 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -1,25 +1,27 @@ # Overview -Module Name: C-WIRE Bid Adapter -Module Type: Adagio Adapter -Maintainer: publishers@cwire.ch +``` +Module Name: C-WIRE Bid Adapter +Module Type: Bidder Adapter +Maintainer: devs@cwire.ch +``` ## Description -Connects to C-WIRE demand source to fetch bids. +Prebid.js Adapter for C-Wire. ## Configuration - Below, the list of C-WIRE params and where they can be set. -| Param name | Global config | AdUnit config | Type | Required | -| ---------- | ------------- | ------------- |--------| ---------| -| pageId | | x | number | YES | -| placementId | | x | number | YES | -| refgroups | | x | string | NO | -| cwcreative | | x | string | NO | -| cwapikey | | x | string | NO | +| Param name | URL parameter | AdUnit config | Type | Required | +|-------------|:-------------:|:-------------:|:--------:|:-------------:| +| pageId | | x | number | YES | +| placementId | | x | number | YES | +| cwgroups | x | | string | NO | +| cwcreative | x | | string | NO | +| cwdebug | x | | boolean | NO | +| cwfeatures | x | | string | NO | ### adUnit configuration @@ -32,7 +34,7 @@ var adUnits = [ bidder: 'cwire', mediaTypes: { banner: { - sizes: [[1, 1]], + sizes: [[400, 600]], } }, params: { @@ -40,7 +42,6 @@ var adUnits = [ placementId: 2211521, // required - number cwcreative: '42', // optional - id of creative to force refgroups: 'test-user', // optional - name of group or coma separated list of groups to force - cwapikey: 'api_key_xyz', // optional - api key for integration testing } }] } diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index 71870d6437d..193a1723403 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains } from '../src/utils.js'; +import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains, isFn, isPlainObject } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -84,7 +84,7 @@ export const spec = { /* Generate bid request for banner adunit */ function buildBannerRequest(bidRequest, bidderRequest) { - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let adW = 0; let adH = 0; @@ -135,7 +135,7 @@ function buildNativeRequest(bidRequest, bidderRequest) { let counter = 0; let assets = []; - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let title = deepAccess(bidRequest, 'mediaTypes.native.title'); if (title && title.len) { @@ -196,7 +196,7 @@ function buildNativeRequest(bidRequest, bidderRequest) { /* Generate bid request for video adunit */ function buildVideoRequest(bidRequest, bidderRequest) { - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let sizeObj = getVideoAdUnitSize(bidRequest); @@ -414,6 +414,7 @@ function buildBannerResponse(bidRequest, bidResponse) { } let bidSizes = (deepAccess(bidRequest, 'mediaTypes.banner.sizes')) ? deepAccess(bidRequest, 'mediaTypes.banner.sizes') : bidRequest.sizes; bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -455,6 +456,7 @@ function buildNativeResponse(bidRequest, response) { return; } bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -507,6 +509,7 @@ function buildVideoResponse(bidRequest, response) { let context = bidRequest.mediaTypes.video.context; bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -630,4 +633,21 @@ function getNativeAssestObj(obj, assets) { } } +// BUILD REQUESTS: BIDFLOORS +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.bidfloor) ? bid.params.bidfloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 072715b4bd6..71c9c7aa341 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -8,7 +8,7 @@ import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, bui import { config } from '../src/config.js'; import { getHook, submodule } from '../src/hook.js'; import { auctionManager } from '../src/auctionManager.js'; -import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {getPPID} from '../src/adserver.js'; @@ -119,6 +119,11 @@ export function buildDfpVideoUrl(options) { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + // TODO - need to know what to set here for queryParams... + } + if (!queryParams.ppid) { const ppid = getPPID(); if (ppid != null) { diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index eb69e76a837..99f313b9484 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -354,16 +354,23 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; + const consentParams = []; 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}`; + consentParams.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); } else { - url += `?gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`?gdpr_consent=${gdprConsent.consentString}`); } } + if (uspConsent && typeof uspConsent.consentString === 'string') { + consentParams.push(`usp=${uspConsent.consentString}`); + } + if (consentParams.length > 0) { + url = url + '?' + consentParams.join('&'); + } syncs.push({ type: 'iframe', url: url diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 0ab6def38ae..7b684efab3c 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,7 @@ import {ajax} from '../src/ajax.js'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.3'; +const VERSION = '1.0.5'; /** * @typedef {object} FeedAdApiBidRequest @@ -16,7 +16,8 @@ const VERSION = '1.0.3'; * @property {number} ad_type * @property {string} client_token * @property {string} placement_id - * @property {string} sdk_version + * @property {string} prebid_adapter_version + * @property {string} prebid_sdk_version * @property {boolean} app_hybrid * * @property {string} [app_bundle_id] @@ -181,7 +182,8 @@ function createApiBidRParams(request) { ad_type: 0, client_token: request.params.clientToken, placement_id: request.params.placementId, - sdk_version: `prebid_${VERSION}`, + prebid_adapter_version: VERSION, + prebid_sdk_version: '$prebid.version$', app_hybrid: false, }); } @@ -207,7 +209,6 @@ function buildRequests(validBidRequests, bidderRequest) { }) }); data.bids.forEach(bid => BID_METADATA[bid.bidId] = { - // TODO: is 'page' the right value here? referer: data.refererInfo.page, transactionId: bid.transactionId }); @@ -266,7 +267,8 @@ function createTrackingParams(data, klass) { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: VERSION + prebid_adapter_version: VERSION, + prebid_sdk_version: '$prebid.version$', }; } @@ -294,12 +296,20 @@ function trackingHandlerFactory(klass) { /** * Reads the user syncs off the server responses and converts them into Prebid.JS format * @param {SyncOptions} syncOptions - * @param {FeedAdApiBidResponse[]} serverResponses + * @param {ServerResponse[]} serverResponses * @param gdprConsent * @param uspConsent */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - return serverResponses.map(response => response.ext) + return serverResponses.map(response => { + // validate response format + const ext = deepAccess(response, 'body.ext', []); + if (ext == null) { + return null; + } + return ext; + }) + .filter(ext => ext != null) .flatMap(extension => { // extract user syncs from extension const pixels = syncOptions.pixelEnabled && extension.pixels ? extension.pixels : []; diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md index 638c966883a..238881db96b 100644 --- a/modules/fpdModule/index.md +++ b/modules/fpdModule/index.md @@ -16,9 +16,11 @@ Validation Submodule: - verify that certain OpenRTB attributes are not specified - optionally suppress user FPD based on the existence of _pubcid_optout +Topic Submodule: +- populate first party/third party topics data onto user.data in bid stream. 1. Module initializes on first load and set bidRequestHook -2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule +2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations/topics dependant on submodule 3. After hook complete, it is disabled - meaning module only runs on first auction 4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load @@ -43,4 +45,5 @@ pbjs.setConfig({ At least one of the submodules must be included in order to successfully run the corresponding above operations. enrichmentFpdModule -validationFpdModule \ No newline at end of file +validationFpdModule +topicsFpdModule \ No newline at end of file diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 764e3d6e6c0..b4d8f69d1b4 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -348,7 +348,11 @@ export const spec = { // Add schain object var schain = currentBidRequest.schain; if (schain) { - requestParams.schain = schain; + try { + requestParams.schain = JSON.stringify(schain); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the schain: ' + error); + } } var vastParams = currentBidRequest.params.vastUrlParams; diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 2f10245cd59..a75c03ee1c4 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -8,8 +8,9 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; -import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; +import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; @@ -18,8 +19,9 @@ export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'} /** * Param or default. - * @param {String} param + * @param {String|function} param * @param {String} defaultVal + * @param arg */ function paramOrDefault(param, defaultVal, arg) { if (isFn(param)) { @@ -53,11 +55,11 @@ export const hadronIdSubmodule = { * @returns {Object} */ decode(value) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); + const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); if (isStr(hadronId)) { return {hadronId: hadronId}; } - return (value && typeof value['hadronId'] === 'string') ? { 'hadronId': value['hadronId'] } : undefined; + return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -70,37 +72,40 @@ export const hadronIdSubmodule = { config.params = {}; } const partnerId = config.params.partnerId | 0; - - const url = urlAddParams( - paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid` - ); - + let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); + if (isStr(hadronId)) { + return {id: {hadronId}}; + } const resp = function (callback) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); - if (isStr(hadronId)) { - const responseObj = {hadronId: hadronId}; - callback(responseObj); - } else { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - logError(error); - } + let responseObj = {}; + const callbacks = { + success: response => { + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); } - callback(responseObj); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); + logInfo(`Response from backend is ${responseObj}`); + hadronId = responseObj['hadronId']; + storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); + responseObj = {id: {hadronId}}; } - }; - ajax(url, callbacks, undefined, {method: 'GET'}); - } + callback(responseObj); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + logInfo('HadronId not found in storage, calling backend...'); + const url = urlAddParams( + // config.params.url and config.params.urlArg are not documented + // since their use is for debugging purposes only + paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), + `partner_id=${partnerId}&_it=prebid` + ); + ajax(url, callbacks, undefined, {method: 'GET'}); }; return {callback: resp}; } diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js new file mode 100644 index 00000000000..92b0a1c18db --- /dev/null +++ b/modules/holidBidAdapter.js @@ -0,0 +1,175 @@ +import { + deepAccess, + getBidIdParameter, + isStr, + logMessage, + triggerPixel, +} from '../src/utils.js' +import * as events from '../src/events.js' +import CONSTANTS from '../src/constants.json' +import { BANNER } from '../src/mediaTypes.js' + +import { registerBidder } from '../src/adapters/bidderFactory.js' + +const BIDDER_CODE = 'holid' +const GVLID = 1177 +const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction' +const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html' +const TIME_TO_LIVE = 300 +let wurlMap = {} + +events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler) + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.adUnitID + }, + + buildRequests: function (validBidRequests, _bidderRequest) { + return validBidRequests.map((bid) => { + const requestData = { + ...bid.ortb2, + id: bid.auctionId, + imp: [getImp(bid)], + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(requestData), + bidId: bid.bidId, + } + }) + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = [] + + if (!serverResponse.body.seatbid) { + return [] + } + + serverResponse.body.seatbid.map((response) => { + response.bid.map((bid) => { + const requestId = bidRequest.bidId + const auctionId = bidRequest.auctionId + const wurl = deepAccess(bid, 'ext.prebid.events.win') + const bidResponse = { + requestId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + creativeId: bid.crid, + currency: serverResponse.body.cur, + netRevenue: true, + ttl: TIME_TO_LIVE, + } + + addWurl({ auctionId, requestId, wurl }) + + bidResponses.push(bidResponse) + }) + }) + + return bidResponses + }, + + getUserSyncs(optionsType, serverResponse, gdprConsent, uspConsent) { + if (!serverResponse || serverResponse.length === 0) { + return [] + } + + const syncs = [] + const bidders = getBidders(serverResponse) + + if (optionsType.iframeEnabled && bidders) { + const queryParams = [] + + queryParams.push('bidders=' + bidders) + queryParams.push('gdpr=' + +gdprConsent.gdprApplies) + queryParams.push('gdpr_consent=' + gdprConsent.consentString) + queryParams.push('usp_consent=' + (uspConsent || '')) + + let strQueryParams = queryParams.join('&') + + if (strQueryParams.length > 0) { + strQueryParams = '?' + strQueryParams + } + + syncs.push({ + type: 'iframe', + url: COOKIE_SYNC_ENDPOINT + strQueryParams + '&type=iframe', + }) + + return syncs + } + + return [] + }, +} + +function getImp(bid) { + const imp = { + ext: { + prebid: { + storedrequest: { + id: getBidIdParameter('adUnitID', bid.params), + }, + }, + }, + } + const sizes = + bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes + + if (deepAccess(bid, 'mediaTypes.banner')) { + imp.banner = { + format: sizes.map((size) => { + return { w: size[0], h: size[1] } + }), + } + } + + return imp +} + +function getBidders(serverResponse) { + const bidders = serverResponse + .map((res) => Object.keys(res.body.ext.responsetimemillis || [])) + .flat(1) + + if (bidders.length) { + return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + } +} + +function addWurl(auctionId, adId, wurl) { + if ([auctionId, adId].every(isStr)) { + wurlMap[`${auctionId}${adId}`] = wurl + } +} + +function removeWurl(auctionId, adId) { + delete wurlMap[`${auctionId}${adId}`] +} + +function getWurl(auctionId, adId) { + if ([auctionId, adId].every(isStr)) { + return wurlMap[`${auctionId}${adId}`] + } +} + +function bidWonHandler(bid) { + const wurl = getWurl(bid.auctionId, bid.adId) + if (wurl) { + logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) + triggerPixel(wurl) + removeWurl(bid.auctionId, bid.adId) + } +} + +registerBidder(spec) diff --git a/modules/holidBidAdapter.md b/modules/holidBidAdapter.md new file mode 100644 index 00000000000..1d83918c00a --- /dev/null +++ b/modules/holidBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Holid Bid Adapter +Module Type: Bidder Adapter +Maintainer: richard@holid.se +``` + +# Description + +Currently module supports only banner mediaType. + +# Test Parameters + +## Sample Banner Ad Unit + +```js +var adUnits = [ + { + code: 'bannerAdUnit', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'holid', + params: { + adUnitID: '12345', + }, + }, + ], + }, +] +``` diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index c2c4a97c62e..a0745df37c8 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -247,7 +247,9 @@ class IdFetchFlow { 'gdpr': hasGdpr, 'nbPage': nbPage, 'o': 'pbjs', - 'rf': referer.topmostLocation, + 'tml': referer.topmostLocation, + 'ref': referer.ref, + 'cu': referer.canonicalUrl, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 27e6d0aee02..bd598cf2d04 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -380,8 +380,8 @@ function parseBid(rawBid, currency, bidRequest) { bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; - - if (rawBid.mtype == MEDIA_TYPES.Video) { + // If mtype = video is passed and vastURl is not set, set vastxml + if (rawBid.mtype == MEDIA_TYPES.Video && ((rawBid.ext && !rawBid.ext.vasturl) || !rawBid.ext)) { bid.vastXml = rawBid.adm; } else if (rawBid.ext && rawBid.ext.vasturl) { bid.vastUrl = rawBid.ext.vasturl; @@ -425,7 +425,6 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.adomain && rawBid.adomain.length > 0) { bid.meta.advertiserDomains = rawBid.adomain; } - return bid; } @@ -629,8 +628,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } const r = {}; - const tmax = config.getConfig('bidderTimeout'); - + const tmax = deepAccess(bidderRequest, 'timeout'); // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; @@ -720,6 +718,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (pageUrl) { r.site.page = pageUrl; } + + if (bidderRequest.gppConsent) { + deepSetValue(r, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(r, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } } if (config.getConfig('coppa')) { @@ -856,7 +859,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { currentRequestSize += currentImpressionSize; const fpd = deepAccess(bidderRequest, 'ortb2') || {}; - if (!isEmpty(fpd) && !isFpdAdded) { r.ext.ixdiag.fpd = true; @@ -876,6 +878,23 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } }); + if (fpd.device) { + const sua = {...fpd.device.sua}; + if (!isEmpty(sua)) { + deepSetValue(r, 'device.sua', sua); + } + } + + if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { + if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { + deepSetValue(r, 'regs.gpp', fpd.regs.gpp) + } + + if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { + deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) + } + } + const clonedRObject = deepClone(r); clonedRObject.site = mergeDeep({}, clonedRObject.site, site); diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index 379d8063c42..6264522ad83 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -47,19 +47,26 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba function init() { if (!jwplayer) { - triggerSetupFailure(-1); // TODO: come up with code for player absent + triggerSetupFailure(-1); // TODO: come up with error code schema- player is absent return; } playerVersion = jwplayer.version; if (playerVersion < minimumSupportedPlayerVersion) { - triggerSetupFailure(-2); // TODO: come up with code for version not supported + triggerSetupFailure(-2); // TODO: come up with error code schema - version not supported + return; + } + + if (!document.getElementById(divId)) { + triggerSetupFailure(-3); // TODO: come up with error code schema - missing div id return; } player = jwplayer(divId); - if (player.getState() === undefined) { + if (!player || !player.getState) { + triggerSetupFailure(-4); // TODO: come up with error code schema - factory function failure + } else if (player.getState() === undefined) { setupPlayer(playerConfig); } else { const payload = getSetupCompletePayload(); diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 2c33c3f61d1..6a1c1cf94b0 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -116,7 +116,11 @@ export const spec = { }; if (meta.mediaType == VIDEO) { - bidResponse.vastXml = adUnit.adm; + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; + } } bidResponses.push(bidResponse); diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 1ee3b7331cb..4b0e5055328 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -1,6 +1,6 @@ import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; const GVLID = 1165; @@ -55,7 +55,7 @@ function isBidRequestValid(bid) { } function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain } = bid; + const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; let { bidFloor, ext } = params; const hashUrl = hashCode(topWindowUrl); const uniqueDealId = getUniqueDealId(hashUrl); @@ -89,7 +89,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { bidderVersion: BIDDER_VERSION, prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}`, - schain: schain + schain: schain, + mediaTypes: mediaTypes }; appendUserIdsToRequestPayload(data, userId); @@ -167,11 +168,12 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains } = result; + const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; if (!ad || !price) { return; } - output.push({ + + const response = { requestId: bidId, cpm: price, width: width, @@ -180,11 +182,22 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - ad: ad, meta: { advertiserDomains: advertiserDomains || [] } - }) + }; + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); }); return output; } catch (e) { @@ -268,7 +281,7 @@ export const spec = { code: BIDDER_CODE, version: BIDDER_VERSION, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index de70b0eaccd..9f45daeea29 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,7 +7,7 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/esm/initializer.js'; +import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 832d98e4f83..883c931824b 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -29,6 +29,7 @@ const DAY_MS = 60 * 60 * 24 * 1000; const MISSING_CORE_CONSENT = 111; const GVLID = 95; const ID_HOST = 'id.crwdcntrl.net'; +const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); let cookieDomain; @@ -252,6 +253,13 @@ export const lotamePanoramaIdSubmodule = { usPrivacy = getFromStorage('us_privacy'); } + const getRequestHost = function() { + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + return ID_HOST_COOKIELESS; + } + return ID_HOST; + } + const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { @@ -288,7 +296,7 @@ export const lotamePanoramaIdSubmodule = { const url = buildUrl({ protocol: 'https', - host: ID_HOST, + host: getRequestHost(), pathname: '/id', search: isEmpty(queryParams) ? undefined : queryParams, }); diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 99f384e9eff..c6911897e84 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -643,7 +643,15 @@ magniteAdapter.disableAnalytics = function () { accountId = undefined; resetConfs(); magniteAdapter.originDisableAnalytics(); -} +}; + +magniteAdapter.onDataDeletionRequest = function () { + if (storage.localStorageIsEnabled()) { + storage.removeDataFromLocalStorage(COOKIE_NAME); + } else { + throw Error('Unable to access local storage, no data deleted'); + } +}; magniteAdapter.MODULE_INITIALIZED_TIME = Date.now(); magniteAdapter.referrerHostname = ''; diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js index 3e01d0e5631..5a159b39081 100644 --- a/modules/medianetRtdProvider.js +++ b/modules/medianetRtdProvider.js @@ -59,14 +59,14 @@ function onAuctionInitEvent(auctionInit) { }, SOURCE)); } -function getTargetingData(adUnitCode) { - const adUnits = getAdUnits(undefined, adUnitCode); +function getTargetingData(adUnitCodes, config, consent, auction) { + const adUnits = getAdUnits(auction.adUnits, adUnitCodes); let targetingData = {}; if (window.mnjs.loaded && isFn(window.mnjs.getTargetingData)) { - targetingData = window.mnjs.getTargetingData(adUnitCode, adUnits, SOURCE) || {}; + targetingData = window.mnjs.getTargetingData(adUnitCodes, adUnits, SOURCE) || {}; } const targeting = {}; - adUnitCode.forEach(adUnitCode => { + adUnitCodes.forEach(adUnitCode => { targeting[adUnitCode] = targeting[adUnitCode] || {}; targetingData[adUnitCode] = targetingData[adUnitCode] || {}; targeting[adUnitCode] = { diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index b5d0e15d078..655f5fc3a35 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -271,15 +271,25 @@ export const spec = { }; function replaceUsersyncMacros(url, gdprConsent, uspConsent) { - const { consentString, gdprApplies } = gdprConsent; - - return url.replace( - '{{.GDPR}}', Number(gdprApplies) - ).replace( - '{{.GDPRConsent}}', consentString - ).replace( - '{{.USPrivacy}}', uspConsent - ); + const { consentString, gdprApplies } = gdprConsent || {}; + + if (gdprApplies) { + const gdpr = Number(gdprApplies); + url = url.replace('{{.GDPR}}', gdpr); + + if (gdpr == 1 && consentString && consentString.length > 0) { + url = url.replace('{{.GDPRConsent}}', consentString); + } + } else { + url = url.replace('{{.GDPR}}', 0); + url = url.replace('{{.GDPRConsent}}', ''); + } + + if (uspConsent) { + url = url.replace('{{.USPrivacy}}', uspConsent); + } + + return url; }; function getAdEl(bid) { diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index ff4ca77aac2..6ead44175e9 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -21,7 +21,8 @@ export const spec = { gvlid: GVLID, aliases: [ { code: 'revenuemaker' }, - { code: 'firstid-ssp' }, + { code: 'first-id', gvlid: 1178 }, + { code: 'adwebone' }, ], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, @@ -79,9 +80,6 @@ function createImpObject(bid) { } } }; - if (bid.params.customParams) { - utils.deepSetValue(imp, 'ext.customParams', bid.params.customParams); - } enrichImp(imp, bid, floor); return imp; } @@ -154,9 +152,6 @@ function getFloor(bid, mediaType) { } function enrichImp(imp, bid, floor) { - if (bid.params.customParams) { - utils.deepSetValue(imp, 'ext.customParams', bid.params.customParams); - } if (floor > 0) { imp.bidfloor = floor; imp.bidfloorcur = 'USD'; @@ -251,6 +246,7 @@ function interpretResponse(response, req) { let bids = []; respBody.seatbid.forEach(seatbid => { + const ssp = seatbid.seat; bids = [...bids, ...seatbid.bid.map(bid => { const response = { requestId: bid.impid, @@ -262,11 +258,13 @@ function interpretResponse(response, req) { currency: respBody.cur || 'USD', netRevenue: true, ttl: 120, - bidderCode: allowAlternateBidderCodes ? `n360-${bid.ssp}` : 'nexx360', mediaType: bid.type === 'banner' ? 'banner' : 'video', - meta: { advertiserDomains: bid.adomain }, + meta: { + advertiserDomains: bid.adomain, + demandSource: ssp, + }, }; - // if (bid.dealid) response.dealid = bid.dealid; + if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ssp}`; if (response.mediaType === 'banner') { response.adUrl = bid.adUrl; diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index b5217e77cd6..70238294c38 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -61,6 +61,12 @@ function buildRequests(validBidRequests, bidderRequest) { consentRequired: bidderRequest.gdprConsent.gdprApplies }; } + if (bidderRequest && bidderRequest.gppConsent) { + payload.gppConsent = { + consentString: bidderRequest.gppConsent.gppString, + applicableSections: bidderRequest.gppConsent.applicableSections + } + } if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } @@ -340,7 +346,7 @@ function getSizes(sizes) { return ret; } -function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { let syncs = []; let params = ''; if (gdprConsent) { @@ -351,6 +357,11 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { params += '&gdpr_consent=' + gdprConsent.consentString; } } + if (gppConsent) { + if (typeof gppConsent.gppString === 'string') { + params += '&gpp_consent=' + gppConsent.gppString; + } + } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; } diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 14525cd0cfc..b6f4523bb50 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -268,6 +268,11 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { nocache: new Date().getTime() }; + const userAgentClientHints = deepAccess(bidderRequest, 'ortb2.device.sua'); + if (userAgentClientHints) { + defaultParams.sua = JSON.stringify(userAgentClientHints); + } + const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); if (userDataSegments.length > 0) { defaultParams.sm = userDataSegments; diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js new file mode 100644 index 00000000000..d72a8719bd8 --- /dev/null +++ b/modules/orbitsoftBidAdapter.js @@ -0,0 +1,150 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'orbitsoft'; +let styleParamsMap = { + 'title.family': 'f1', // headerFont + 'title.size': 'fs1', // headerFontSize + 'title.weight': 'w1', // headerWeight + 'title.style': 's1', // headerStyle + 'title.color': 'c3', // headerColor + 'description.family': 'f2', // descriptionFont + 'description.size': 'fs2', // descriptionFontSize + 'description.weight': 'w2', // descriptionWeight + 'description.style': 's2', // descriptionStyle + 'description.color': 'c4', // descriptionColor + 'url.family': 'f3', // urlFont + 'url.size': 'fs3', // urlFontSize + 'url.weight': 'w3', // urlWeight + 'url.style': 's3', // urlStyle + 'url.color': 'c5', // urlColor + 'colors.background': 'c2', // borderColor + 'colors.border': 'c1', // borderColor + 'colors.link': 'c6', // lnkColor +}; +export const spec = { + code: BIDDER_CODE, + aliases: ['oas', '152media'], // short code and customer aliases + isBidRequestValid: function (bid) { + switch (true) { + case !('params' in bid): + utils.logError(bid.bidder + ': No required params'); + return false; + case !(bid.params.placementId): + utils.logError(bid.bidder + ': No required param placementId'); + return false; + case !(bid.params.requestUrl): + utils.logError(bid.bidder + ': No required param requestUrl'); + return false; + } + return true; + }, + buildRequests: function (validBidRequests) { + let bidRequest; + let serverRequests = []; + for (let i = 0; i < validBidRequests.length; i++) { + bidRequest = validBidRequests[i]; + let bidRequestParams = bidRequest.params; + let placementId = utils.getBidIdParameter('placementId', bidRequestParams); + let requestUrl = utils.getBidIdParameter('requestUrl', bidRequestParams); + let referrer = utils.getBidIdParameter('ref', bidRequestParams); + let location = utils.getBidIdParameter('loc', bidRequestParams); + // Append location & referrer + if (location === '') { + location = utils.getWindowLocation(); + } + if (referrer === '' && bidRequest && bidRequest.refererInfo) { + referrer = bidRequest.refererInfo.referer; + } + + // Styles params + let stylesParams = utils.getBidIdParameter('style', bidRequestParams); + let stylesParamsArray = {}; + for (let currentValue in stylesParams) { + if (stylesParams.hasOwnProperty(currentValue)) { + let currentStyle = stylesParams[currentValue]; + for (let field in currentStyle) { + if (currentStyle.hasOwnProperty(field)) { + let styleField = styleParamsMap[currentValue + '.' + field]; + if (typeof styleField !== 'undefined') { + stylesParamsArray[styleField] = currentStyle[field]; + } + } + } + } + } + // Custom params + let customParams = utils.getBidIdParameter('customParams', bidRequestParams); + let customParamsArray = {}; + for (let customField in customParams) { + if (customParams.hasOwnProperty(customField)) { + customParamsArray['c.' + customField] = customParams[customField]; + } + } + + // Sizes params (not supports by server, for future features) + let sizesParams = bidRequest.sizes; + let parsedSizes = utils.parseSizesInput(sizesParams); + let requestData = Object.assign({ + 'scid': placementId, + 'callback_uid': utils.generateUUID(), + 'loc': location, + 'ref': referrer, + 'size': parsedSizes + }, stylesParamsArray, customParamsArray); + + serverRequests.push({ + method: 'POST', + url: requestUrl, + data: requestData, + options: {withCredentials: false}, + bidRequest: bidRequest + }); + } + return serverRequests; + }, + interpretResponse: function (serverResponse, request) { + let bidResponses = []; + if (!serverResponse || serverResponse.error) { + utils.logError(BIDDER_CODE + ': Server response error'); + return bidResponses; + } + + const serverBody = serverResponse.body; + if (!serverBody) { + utils.logError(BIDDER_CODE + ': Empty bid response'); + return bidResponses; + } + + const CPM = serverBody.cpm; + const WIDTH = serverBody.width; + const HEIGHT = serverBody.height; + const CREATIVE = serverBody.content_url; + const CALLBACK_UID = serverBody.callback_uid; + const TIME_TO_LIVE = config.getConfig('_bidderTimeout'); + const REFERER = utils.getWindowTop(); + let bidRequest = request.bidRequest; + if (CPM > 0 && WIDTH > 0 && HEIGHT > 0) { + let bidResponse = { + requestId: bidRequest.bidId, + cpm: CPM, + width: WIDTH, + height: HEIGHT, + creativeId: CALLBACK_UID, + ttl: TIME_TO_LIVE, + referrer: REFERER, + currency: 'USD', + netRevenue: true, + adUrl: CREATIVE, + meta: { + advertiserDomains: serverBody.adomain ? serverBody.adomain : [] + } + }; + bidResponses.push(bidResponse); + } + + return bidResponses; + } +}; +registerBidder(spec); diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 3db1da0d689..6bcbc6a1cba 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -4,11 +4,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray } from '../src/utils.js'; +import { NATIVE, BANNER, VIDEO } from '../src/mediaTypes.js'; +import { OUTSTREAM } from '../src/video.js'; +import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'outbrain'; const GVLID = 164; @@ -22,11 +24,12 @@ const NATIVE_PARAMS = { body: { id: 4, name: 'data', type: 2 }, cta: { id: 1, type: 12, name: 'data' } }; +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [ NATIVE, BANNER ], + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], isBidRequestValid: (bid) => { if (typeof bid.params !== 'object') { return false; @@ -50,7 +53,7 @@ export const spec = { return ( !!config.getConfig('outbrain.bidderUrl') && - !!(bid.nativeParams || bid.sizes) + (!!(bid.nativeParams || bid.sizes) || isValidVideoRequest(bid)) ); }, buildRequests: (validBidRequests, bidderRequest) => { @@ -85,6 +88,8 @@ export const spec = { assets: getNativeAssets(bid) }) } + } else if (isVideoRequest(bid)) { + imp.video = getVideoAsset(bid); } else { imp.banner = { format: transformSizes(bid.sizes) @@ -163,7 +168,12 @@ export const spec = { return bids.map((bid, id) => { const bidResponse = bidResponses[id]; if (bidResponse) { - const type = bid.nativeParams ? NATIVE : BANNER; + let type = BANNER; + if (bid.nativeParams) { + type = NATIVE; + } else if (isVideoRequest(bid)) { + type = VIDEO; + } const bidObject = { requestId: bid.bidId, cpm: bidResponse.price, @@ -176,10 +186,16 @@ export const spec = { }; if (type === NATIVE) { bidObject.native = parseNative(bidResponse); - } else { + } else if (type === BANNER) { bidObject.ad = bidResponse.adm; bidObject.width = bidResponse.w; bidObject.height = bidResponse.h; + } else if (type === VIDEO) { + bidObject.vastXml = bidResponse.adm; + const videoContext = deepAccess(bid, 'mediaTypes.video.context'); + if (videoContext === OUTSTREAM) { + bidObject.renderer = createRenderer(bid); + } } bidObject.meta = {}; if (bidResponse.adomain && bidResponse.adomain.length > 0) { @@ -304,6 +320,27 @@ function getNativeAssets(bid) { }).filter(Boolean); } +function getVideoAsset(bid) { + const sizes = flatten(bid.mediaTypes.video.playerSize); + return { + w: parseInt(sizes[0], 10), + h: parseInt(sizes[1], 10), + protocols: bid.mediaTypes.video.protocols, + playbackmethod: bid.mediaTypes.video.playbackmethod, + mimes: bid.mediaTypes.video.mimes, + skip: bid.mediaTypes.video.skip, + delivery: bid.mediaTypes.video.delivery, + api: bid.mediaTypes.video.api, + minbitrate: bid.mediaTypes.video.minbitrate, + maxbitrate: bid.mediaTypes.video.maxbitrate, + minduration: bid.mediaTypes.video.minduration, + maxduration: bid.mediaTypes.video.maxduration, + startdelay: bid.mediaTypes.video.startdelay, + placement: bid.mediaTypes.video.placement, + linearity: bid.mediaTypes.video.linearity + }; +} + /* Turn bid request sizes into ut-compatible format */ function transformSizes(requestSizes) { if (!isArray(requestSizes)) { @@ -338,3 +375,63 @@ function _getFloor(bid, type) { } return null; } + +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +function createRenderer(bid) { + let config = {}; + let playerUrl = OUTSTREAM_RENDERER_URL; + let render = function (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: bid.sizes, + targetId: bid.adUnitCode, + adResponse: { content: bid.vastXml } + }); + }); + }; + + let externalRenderer = deepAccess(bid, 'mediaTypes.video.renderer'); + if (!externalRenderer) { + externalRenderer = deepAccess(bid, 'renderer'); + } + + if (externalRenderer) { + config = externalRenderer.options; + playerUrl = externalRenderer.url; + render = externalRenderer.render; + } + + const renderer = Renderer.install({ + id: bid.adUnitCode, + url: playerUrl, + config: config, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + renderer.setRender(render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function isValidVideoRequest(bid) { + const videoAdUnit = deepAccess(bid, 'mediaTypes.video') + if (!videoAdUnit) { + return false; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (videoAdUnit.context == '') { + return false; + } + + return true; +} diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index d62834cfcfc..305e175bf2c 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -243,7 +243,7 @@ function getCustomBidderFn (moduleConfig, bidder) { */ function getDefaultBidderFn (bidder) { const isPStandardTargetingEnabled = (data, acEnabled) => { - return (acEnabled && data.ac && data.ac.length) || (data.ssp && data.ssp.cohorts.length) + return (acEnabled && data.ac && data.ac.length) || (data.ssp && data.ssp.cohorts && data.ssp.cohorts.length) } const pStandardTargeting = (data, acEnabled) => { const ac = (acEnabled) ? (data.ac ?? []) : [] @@ -311,16 +311,19 @@ export function isPermutiveOnPage () { * @return {Object} */ export function getSegments (maxSegs) { - const legacySegs = readSegments('_psegs').map(Number).filter(seg => seg >= 1000000).map(String) - const _ppam = readSegments('_ppam') - const _pcrprs = readSegments('_pcrprs') + const legacySegs = readSegments('_psegs', []).map(Number).filter(seg => seg >= 1000000).map(String) + const _ppam = readSegments('_ppam', []) + const _pcrprs = readSegments('_pcrprs', []) const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], - rubicon: readSegments('_prubicons'), - appnexus: readSegments('_papns'), - gam: readSegments('_pdfps'), - ssp: readSegments('_pssps'), + rubicon: readSegments('_prubicons', []), + appnexus: readSegments('_papns', []), + gam: readSegments('_pdfps', []), + ssp: readSegments('_pssps', { + cohorts: [], + ssps: [] + }), } for (const bidder in segments) { @@ -338,15 +341,17 @@ export function getSegments (maxSegs) { /** * Gets an array of segment IDs from LocalStorage - * or returns an empty array + * or return the default value provided. + * @template A * @param {string} key - * @return {string[]|number[]} + * @param {A} defaultValue + * @return {A} */ -function readSegments (key) { +function readSegments (key, defaultValue) { try { - return JSON.parse(storage.getDataFromLocalStorage(key) || '[]') + return JSON.parse(storage.getDataFromLocalStorage(key)) || defaultValue } catch (e) { - return [] + return defaultValue } } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 072c280aecf..924748ce197 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -216,16 +216,29 @@ export function resetSyncedStatus() { /** * @param {Array} bidderCodes list of bidders to request user syncs for. */ -function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { +function queueSync(bidderCodes, gdprConsent, uspConsent, gppConsent, s2sConfig) { if (_s2sConfigs.length === _syncCount) { return; } _syncCount++; + let filterSettings = {}; + const userSyncFilterSettings = getConfig('userSync.filterSettings'); + + if (userSyncFilterSettings) { + const { all, iframe, image } = userSyncFilterSettings; + const ifrm = iframe || all; + const img = image || all; + + if (ifrm) filterSettings = Object.assign({ iframe: ifrm }, filterSettings); + if (img) filterSettings = Object.assign({ image: img }, filterSettings); + } + const payload = { uuid: generateUUID(), bidders: bidderCodes, - account: s2sConfig.accountId + account: s2sConfig.accountId, + filterSettings }; let userSyncLimit = s2sConfig.userSyncLimit; @@ -246,6 +259,15 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { payload.us_privacy = uspConsent; } + if (gppConsent) { + // proposing the following formatting, can adjust if needed... + // update - leaving this param as an array, since it's part of a POST payload where the [] characters shouldn't matter too much + payload.gpp_sid = gppConsent.applicableSections + // should we add check if applicableSections was not equal to -1 (where user was out of scope)? + // this would be similar to what was done above for TCF + payload.gpp = gppConsent.gppString; + } + if (typeof s2sConfig.coopSync === 'boolean') { payload.coopSync = s2sConfig.coopSync; } @@ -330,7 +352,7 @@ function doBidderSync(type, url, bidder, done, timeout) { * * @param {Array} bidders a list of bidder names */ -function doClientSideSyncs(bidders, gdprConsent, uspConsent) { +function doClientSideSyncs(bidders, gdprConsent, uspConsent, gppConsent) { bidders.forEach(bidder => { let clientAdapter = adapterManager.getBidAdapter(bidder); if (clientAdapter && clientAdapter.registerSyncs) { @@ -341,7 +363,8 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent) { clientAdapter, [], gdprConsent, - uspConsent + uspConsent, + gppConsent ) ); } @@ -411,12 +434,13 @@ function getMatchingConsentUrl(urlProp, gdprConsent) { } function getConsentData(bidRequests) { - let gdprConsent, uspConsent; + let gdprConsent, uspConsent, gppConsent; if (Array.isArray(bidRequests) && bidRequests.length > 0) { gdprConsent = bidRequests[0].gdprConsent; uspConsent = bidRequests[0].uspConsent; + gppConsent = bidRequests[0].gppConsent; } - return { gdprConsent, uspConsent }; + return { gdprConsent, uspConsent, gppConsent }; } /** @@ -433,7 +457,7 @@ export function PrebidServer() { done = adapterMetrics.startTiming('total').stopBefore(done); bidRequests.forEach(req => useMetrics(req.metrics).join(adapterMetrics, {continuePropagation: false})); - let { gdprConsent, uspConsent } = getConsentData(bidRequests); + let { gdprConsent, uspConsent, gppConsent } = getConsentData(bidRequests); if (Array.isArray(_s2sConfigs)) { if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { @@ -441,7 +465,7 @@ export function PrebidServer() { .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) .filter((bidder, index, array) => (array.indexOf(bidder) === index)); - queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); + queueSync(syncBidders, gdprConsent, uspConsent, gppConsent, s2sBidRequest.s2sConfig); } processPBSRequest(s2sBidRequest, bidRequests, ajax, { @@ -450,7 +474,7 @@ export function PrebidServer() { bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } done(); - doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); + doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, onError: done, onBid: function ({adUnit, bid}) { diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 0aee261c25d..e35a3825826 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -47,7 +47,7 @@ const PBS_CONVERTER = ortbConverter({ request.tmax = s2sBidRequest.s2sConfig.timeout; deepSetValue(request, 'source.tid', proxyBidderRequest.auctionId); - [request.app, request.site].forEach(section => { + [request.app, request.dooh, request.site].forEach(section => { if (section && !section.publisher?.id) { deepSetValue(section, 'publisher.id', s2sBidRequest.s2sConfig.accountId); } @@ -246,6 +246,7 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste impIds.add(impId) proxyBidRequests.push({ ...adUnit, + adUnitCode: adUnit.code, ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), pbsData: {impId, actualBidRequests, adUnit} }); diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 8b5976149d0..92aecb0ca50 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -28,6 +28,7 @@ import {auctionManager} from '../src/auctionManager.js'; import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; import {beConvertCurrency} from '../src/utils/currency.js'; +import {adjustCpm} from '../src/utils/cpm.js'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -179,12 +180,8 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { /** * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ -export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { - const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); - if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); - } - return parseFloat(inputCpm); +export function getBiddersCpmAdjustment(inputCpm, bid, bidRequest) { + return parseFloat(adjustCpm(inputCpm, {...bid, cpm: inputCpm}, bidRequest)); } /** @@ -249,8 +246,14 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: // if cpmAdjustment flag is true and we have a valid floor then run the adjustment on it if (floorData.enforcement.bidAdjustment && floorInfo.matchingFloor) { - let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor); - floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + // pub provided inverse function takes precedence, otherwise do old adjustment stuff + const inverseFunction = bidderSettings.get(bidRequest.bidder, 'inverseBidAdjustment'); + if (inverseFunction) { + floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); + } else { + let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest); + floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + } } if (floorInfo.matchingFloor) { @@ -307,6 +310,8 @@ export function getFloorDataFromAdUnits(adUnits) { // copy over the new rules into our values object Object.assign(accum.values, newRules); } + } else if (adUnit.floors != null) { + logWarn(`adUnit '${adUnit.code}' provides an invalid \`floor\` definition, it will be ignored for floor calculations`, adUnit); } return accum; }, {}); @@ -731,7 +736,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid); + adjustedCpm = getBiddersCpmAdjustment(adjustedCpm, bid, matchingBidRequest); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 25f82fb60d9..015e80d5692 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -16,6 +16,7 @@ const DEFAULT_BID_TTL = 20; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'video', 'battr', 'bcat', 'badv', 'bidfloor']; +const DEFAULT_TMAX = 500; /** * PulsePoint Bid Adapter. @@ -54,6 +55,7 @@ export const spec = { user: user(bidRequests[0], bidderRequest), regs: regs(bidderRequest), source: source(bidRequests[0].schain), + tmax: bidderRequest.timeout || DEFAULT_TMAX, }; return { method: 'POST', diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 4325a699a3b..0c4d6148f2b 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -400,7 +400,8 @@ function generateGeneralParams(generalObject, bidderRequest) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, device_type: getDeviceType(navigator.userAgent), ua: navigator.userAgent, - session_id: getBidIdParameter('auctionId', generalObject), + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('auctionId', generalObject), tmax: timeout }; diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 9dab3ec2a30..f0837cb5508 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -25,6 +25,8 @@ The adapter supports Video(instream). | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false | `rtbDomain` | optional | String | Sets the seller end point | "www.test.com" +| `is_wrapper` | private | Boolean | Please don't use unless your account manager asked you to | false + # Test Parameters ```javascript diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 876da8dda37..05594c63132 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -163,7 +163,7 @@ import {getHook, module} from '../../src/hook.js'; import {logError, logInfo, logWarn} from '../../src/utils.js'; import * as events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; -import adapterManager, {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js'; import {find} from '../../src/polyfill.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; @@ -246,6 +246,7 @@ function getConsentData() { return { gdpr: gdprDataHandler.getConsentData(), usp: uspDataHandler.getConsentData(), + gpp: gppDataHandler.getConsentData(), coppa: !!(config.getConfig('coppa')) } } diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 64b9cd5d4aa..29953da7ffa 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -2,120 +2,111 @@ 'use strict'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE } from '../src/mediaTypes.js'; -import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import { _map, isArray, isEmpty, deepSetValue, replaceAuctionPrice } from '../src/utils.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'seedingAlliance'; const GVL_ID = 371; +const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; -const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; +const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - - body: { - id: 1, - name: 'data', - type: 2 - }, - - sponsoredBy: { - id: 2, - name: 'data', - type: 1 - }, - - image: { - id: 3, - type: 3, - name: 'img' - }, - - cta: { - id: 4, - type: 12, - name: 'data' - }, - - icon: { - id: 5, - type: 1, - name: 'img' - } + title: { id: 0, name: 'title' }, + body: { id: 1, name: 'data', type: 2 }, + sponsoredBy: { id: 2, name: 'data', type: 1 }, + image: { id: 3, type: 3, name: 'img' }, + cta: { id: 4, type: 12, name: 'data' }, + icon: { id: 5, type: 1, name: 'img' } }; export const spec = { code: BIDDER_CODE, - gvlid: GVL_ID, + supportedMediaTypes: [NATIVE, BANNER], - supportedMediaTypes: [NATIVE], - - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return !!bid.params.adUnitId; }, - buildRequests: (validBidRequests, bidderRequest) => { + buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.auctionId; - const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; let url = bidderRequest.refererInfo.page; - const imp = validBidRequests.map((bid, id) => { - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - - const asset = { - required: bidParams.required & 1 - }; + const imps = validBidRequests.map((bidRequest, id) => { + const imp = { + id: String(id + 1), + tagid: bidRequest.params.adUnitId + }; - if (props) { - asset.id = props.id; + /** + * Native Ad + */ + if (bidRequest.nativeParams) { + const assets = _map(bidRequest.nativeParams, (nativeAsset, key) => { + const props = NATIVE_PARAMS[key]; + + if (props) { + let wmin, hmin, w, h; + let aRatios = nativeAsset.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } - let w, h; + if (nativeAsset.sizes) { + const sizes = flatten(nativeAsset.sizes); + w = parseInt(sizes[0], 10); + h = parseInt(sizes[1], 10); + } - if (bidParams.sizes) { - w = bidParams.sizes[0]; - h = bidParams.sizes[1]; + const asset = { + id: props.id, + required: nativeAsset.required & 1 + }; + + asset[props.name] = { + len: nativeAsset.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } else { + // TODO Filter impressions with required assets we don't support } + }).filter(Boolean); - asset[props.name] = { - len: bidParams.len, - type: props.type, - w, - h - }; + imp.native = { + request: { + assets + } + }; + } else { + let sizes = transformSizes(bidRequest.sizes); - return asset; + imp.banner = { + format: sizes, + w: sizes[0] ? sizes[0].w : 0, + h: sizes[0] ? sizes[0].h : 0 } - }) - .filter(Boolean); + } - if (bid.params.url) { - url = bid.params.url; + if (bidRequest.params.url) { + url = bidRequest.params.url; } - return { - id: String(id + 1), - tagid: bid.params.adUnitId, - tid: tid, - pt: pt, - native: { - request: { - assets - } - } - }; + return imp; }); const request = { @@ -123,12 +114,9 @@ export const spec = { site: { page: url }, - device: { - ua: navigator.userAgent - }, - cur, - imp, - user: {}, + cur: [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR], + imp: imps, + tmax: bidderRequest.timeout, regs: { ext: { gdpr: 0, @@ -137,23 +125,22 @@ export const spec = { } }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { + request.user = {}; + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); } return { method: 'POST', - url: ENDPOINT_URL, + url: config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL, data: JSON.stringify(request), - options: { - contentType: 'application/json' - }, - bids: validBidRequests + bidRequests: validBidRequests }; }, - interpretResponse: function(serverResponse, { bids }) { + interpretResponse: function (serverResponse, { bidRequests }) { if (isEmpty(serverResponse.body)) { return []; } @@ -165,35 +152,72 @@ export const spec = { return result; }, []) : []; - return bids - .map((bid, id) => { + return bidRequests + .map((bidRequest, id) => { const bidResponse = bidResponses[id]; + const type = bidRequest.nativeParams ? NATIVE : BANNER; + if (bidResponse) { - return { - requestId: bid.bidId, + const bidObject = { + requestId: bidRequest.bidId, // TODO get this value from response? cpm: bidResponse.price, creativeId: bidResponse.crid, - ttl: 1000, - netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), + ttl: 600, + netRevenue: true, currency: cur, - mediaType: NATIVE, + mediaType: type, bidderCode: BIDDER_CODE, - native: parseNative(bidResponse), meta: { advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] } }; + + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + bidObject.mediaType = NATIVE; + } + + if (type === BANNER) { + bidObject.ad = replaceAuctionPrice(bidResponse.adm, bidResponse.price); + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + bidObject.mediaType = BANNER; + } + + return bidObject; } }) .filter(Boolean); } }; -registerBidder(spec); +function transformSizes(requestSizes) { + if (!isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (isArray(requestSizes[0])) { + return requestSizes.map(item => ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + })); + } + + return []; +} + +function flatten(arr) { + return [].concat(...arr); +} function parseNative(bid) { - const {assets, link, imptrackers} = bid.adm.native; + const { assets, link, imptrackers } = bid.adm.native; let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -228,15 +252,4 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} +registerBidder(spec); diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 1a4f903789b..f54245a41ab 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -60,6 +60,10 @@ function hasVideoMediaType(bid) { return !!bid.mediaTypes && !!bid.mediaTypes.video; } +function hasBannerMediaType(bid) { + return !!bid.mediaTypes && !!bid.mediaTypes.banner; +} + function hasMandatoryDisplayParams(bid) { const p = bid.params; return ( @@ -72,17 +76,27 @@ function hasMandatoryDisplayParams(bid) { function hasMandatoryVideoParams(bid) { const videoParams = getVideoParams(bid); - return ( + let isValid = !!bid.params.publisherId && !!bid.params.adUnitId && hasVideoMediaType(bid) && !!videoParams.playerSize && isArray(videoParams.playerSize) && - videoParams.playerSize.length > 0 && - // only instream is supported for video - videoParams.context === 'instream' && - bid.params.placement === 'inStream' - ); + videoParams.playerSize.length > 0; + + switch (bid.params.placement) { + // instream accept only video format + case 'inStream': + return isValid && videoParams.context === 'instream'; + // outstream accept banner/native/video format + default: + return ( + isValid && + videoParams.context === 'outstream' && + hasBannerMediaType(bid) && + hasMandatoryDisplayParams(bid) + ); + } } function buildBidRequest(validBidRequest) { @@ -260,6 +274,18 @@ export const spec = { payload.coppa = coppa; } + if (bidderRequest.gppConsent) { + payload.gppConsent = { + gppString: bidderRequest.gppConsent.gppString, + applicableSections: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest.ortb2?.regs?.gpp) { + payload.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } + const payloadString = JSON.stringify(payload); return { method: 'POST', diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 6ff0e592542..c07b2abe933 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -13,6 +13,7 @@ export const spec = { gvlid: GVL_ID, aliases: ['smart'], // short code supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * @@ -131,7 +132,6 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - const adServerCurrency = config.getConfig('currency.adServerCurrency'); const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); @@ -144,7 +144,6 @@ export const spec = { pageid: bid.params.pageId, formatid: bid.params.formatId, currencyCode: adServerCurrency, - bidfloor: bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency), targeting: bid.params.target && bid.params.target !== '' ? bid.params.target : undefined, buid: bid.params.buId && bid.params.buId !== '' ? bid.params.buId : undefined, appname: bid.params.appName && bid.params.appName !== '' ? bid.params.appName : undefined, @@ -161,10 +160,21 @@ export const spec = { sdc: sellerDefinedContext }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + + if (bidderRequest.gppConsent) { + payload.gpp = bidderRequest.gppConsent.gppString; + payload.gpp_sid = bidderRequest.gppConsent.applicableSections; + } + + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } } if (bid && bid.userId) { @@ -175,24 +185,28 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); - const isAdUnitContainingVideo = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); - if (!isAdUnitContainingVideo && bannerMediaType) { - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && !bannerMediaType) { - spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && bannerMediaType) { - // If there are video and banner media types in the ad unit, we clone the payload - // to create a specific one for video. - let videoPayload = deepClone(payload); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const isSupportedVideoContext = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); + + if (bannerMediaType || isSupportedVideoContext) { + let type; + if (bannerMediaType) { + type = BANNER; + payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + if (isSupportedVideoContext) { + let videoPayload = deepClone(payload); + spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); + videoPayload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, VIDEO); + bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + } + } else { + type = VIDEO; + spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); + } - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); + payload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, type); bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); } else { bidRequests.push({}); @@ -253,24 +267,21 @@ export const spec = { * * @param {object} bid Bid request object * @param {string} currency Ad server currency + * @param {string} mediaType Bid media type * @return {number} Floor price */ - getBidFloor: function (bid, currency) { + getBidFloor: function (bid, currency, mediaType) { if (!isFn(bid.getFloor)) { return DEFAULT_FLOOR; } const floor = bid.getFloor({ currency: currency || 'USD', - mediaType: '*', + mediaType, size: '*' }); - if (isPlainObject(floor) && !isNaN(floor.floor)) { - return floor.floor; - } - - return DEFAULT_FLOOR; + return isPlainObject(floor) && !isNaN(floor.floor) ? floor.floor : DEFAULT_FLOOR; }, /** diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 452aaafb09b..bfc664180a3 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -76,6 +76,7 @@ export const spec = { const bidfloor = getBidFloor(bid) || 0; const bidfloorcur = getBidIdParameter('bidfloorcur', bid.params) || 'EUR'; const siteId = getBidIdParameter('siteId', bid.params); + const sitekey = getBidIdParameter('sitekey', bid.params); const domain = getBidIdParameter('domain', bid.params); const cat = getBidIdParameter('cat', bid.params) || ['']; let pubcid = null; @@ -191,6 +192,11 @@ export const spec = { } } + // Add sitekey if available + if (sitekey) { + requestPayload.site.content.ext.sitekey = sitekey; + } + // Add common id if available if (pubcid) { userExt.fpc = pubcid; diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index f078d905e62..e0d6023a794 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -10,6 +10,15 @@ Maintainer: supply@smartyads.com Module that connects to SmartyAds' demand sources +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `sourceid` | required (for prebid.js) | placement ID | "0" | +| `host` | required (for prebid-server) | const value, set to "prebid" | "prebid" | +| `accountid` | required (for prebid-server) | partner ID | "1901" | +| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | + # Test Parameters ``` var adUnits = [ diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index 231ca315de8..9f275a761c7 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -1,29 +1,77 @@ +import {buildUrl, deepAccess} from '../src/utils.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {buildUrl} from '../src/utils.js' const BIDDER_CODE = 'smartytech'; export const ENDPOINT_PROTOCOL = 'https'; export const ENDPOINT_DOMAIN = 'server.smartytech.io'; -export const ENDPOINT_PATH = '/hb/bidder'; +export const ENDPOINT_PATH = '/hb/v2/bidder'; export const spec = { + supportedMediaTypes: [ BANNER, VIDEO ], code: BIDDER_CODE, isBidRequestValid: function (bidRequest) { - return !!parseInt(bidRequest.params.endpointId); + return ( + !!parseInt(bidRequest.params.endpointId) && + spec._validateBanner(bidRequest) && + spec._validateVideo(bidRequest) + ); + }, + + _validateBanner: function(bidRequest) { + const bannerAdUnit = deepAccess(bidRequest, 'mediaTypes.banner'); + + if (bannerAdUnit === undefined) { + return true; + } + + if (!Array.isArray(bannerAdUnit.sizes)) { + return false; + } + + return true; + }, + + _validateVideo: function(bidRequest) { + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); + + if (videoAdUnit === undefined) { + return true; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (!videoAdUnit.context) { + return false; + } + + return true; }, buildRequests: function (validBidRequests, bidderRequest) { const referer = bidderRequest?.refererInfo?.page || window.location.href; const bidRequests = validBidRequests.map((validBidRequest) => { - return { + let video = deepAccess(validBidRequest, 'mediaTypes.video', false); + let banner = deepAccess(validBidRequest, 'mediaTypes.banner', false); + + let oneRequest = { endpointId: validBidRequest.params.endpointId, adUnitCode: validBidRequest.adUnitCode, - sizes: validBidRequest.sizes, - bidId: validBidRequest.bidId, - referer: referer + referer: referer, + bidId: validBidRequest.bidId }; + + if (video) { + oneRequest.video = video; + } else if (banner) { + oneRequest.banner = banner; + } + + return oneRequest }); let adPartnerRequestUrl = buildUrl({ @@ -55,12 +103,14 @@ export const spec = { bid: validBids.find(b => b.adUnitCode === key), response: responseBody[key] } - }).map(item => spec.adResponse(item.bid.bidId, item.response)); + }).map(item => spec._adResponse(item.bid, item.response)); }, - adResponse: function (requestId, response) { + _adResponse: function (request, response) { const bidObject = { - requestId, + requestId: request.bidId, + adUnitCode: request.adUnitCode, + bidderCode: BIDDER_CODE, ad: response.ad, cpm: response.cpm, width: response.width, @@ -69,7 +119,14 @@ export const spec = { creativeId: response.creativeId, netRevenue: true, currency: response.currency, + mediaType: BANNER } + + if (response.mediaType === VIDEO) { + bidObject.vastXml = response.ad; + bidObject.mediaType = VIDEO; + } + return bidObject; }, diff --git a/modules/smartytechBidAdapter.md b/modules/smartytechBidAdapter.md index dbfc2833c78..9df57ddbde7 100644 --- a/modules/smartytechBidAdapter.md +++ b/modules/smartytechBidAdapter.md @@ -1,44 +1,55 @@ # Overview -Module Name: SmartyTech Bidder Adapter - -Module Type: Bidder Adapter - +``` +Module Name: SmartyTech Bid Adapter +Module Type: Bidder Adapter Maintainer: info@adpartner.pro +``` # Description -You can use this adapter to get a bid from smartytech.io. +Connects to SmartyTech's exchange for bids. -About us : https://smartytech.io +SmartyTech bid adapter supports Banner and Video -# Test Parameters +# Sample Ad Unit: For Publishers +## Sample Banner Ad Unit +``` +var adUnits = [{ + code: '/123123123/prebidjs-banner', + mediaTypes: { + banner: { + sizes: [ + [300, 301], + [300, 250] + ] + } + }, + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}]; +``` -```javascript - var adUnits = [ - { - code: 'div-smartytech-example', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] +## Sample Video Ad Unit +``` +var videoAdUnit = { + code: '/123123123/video-vast-banner', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + } }, - { - code: 'div-smartytech-example-2', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] - } -]; + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}; ``` diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 3c841cc4d8a..f9cc1f3b353 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -295,22 +295,29 @@ function _findBidderRequest(bidderRequests, bidId) { } } +// This function takes all the possible sizes. +// returns string csv. function _validateSize(bid) { - if (deepAccess(bid, 'mediaTypes.video')) { - return ''; // Video bids arent allowed to override sizes via the trinity request + let size = []; + if (deepAccess(bid, 'mediaTypes.video.playerSize')) { + size.push(deepAccess(bid, 'mediaTypes.video.playerSize')) } - - if (bid.params.sizes) { - return parseSizesInput(bid.params.sizes).join(','); + if (deepAccess(bid, 'mediaTypes.video.sizes')) { + size.push(deepAccess(bid, 'mediaTypes.video.sizes')) + } + if (deepAccess(bid, 'params.sizes')) { + size.push(deepAccess(bid, 'params.sizes')); } if (deepAccess(bid, 'mediaTypes.banner.sizes')) { - return parseSizesInput(deepAccess(bid, 'mediaTypes.banner.sizes')).join(','); + size.push(deepAccess(bid, 'mediaTypes.banner.sizes')) } - - // Handle deprecated sizes definition - if (bid.sizes) { - return parseSizesInput(bid.sizes).join(','); + if (deepAccess(bid, 'sizes')) { + size.push(deepAccess(bid, 'sizes')) } + // Pass the 2d sizes array into parseSizeInput to flatten it into an array of x separated sizes. + // Then throw it into Set to uniquify it. + // Then spread it to an array again. Then join it into a csv of sizes. + return [...new Set(parseSizesInput(...size))].join(','); } function _validateSlot(bid) { diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 670d28ab64e..026d5a098df 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,13 +3,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {getWindowSelf} from '../src/utils.js' +import {deepAccess, getWindowSelf, replaceAuctionPrice} from '../src/utils.js' import {getStorageManager} from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; export const END_POINT_URL = 'https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction'; +export const USER_SYNC_IMG_URL = 'https://trc.taboola.com/sg/prebidJS/1/cm'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; @@ -81,7 +83,7 @@ export const spec = { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const {publisherId} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, refererInfo); + const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); const user = { @@ -102,13 +104,19 @@ export const spec = { regs.ext.us_privacy = uspConsent; } + if (bidderRequest.ortb2?.regs?.gpp) { + regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + if (config.getConfig('coppa')) { regs.coppa = 1; } const ortb2 = bidderRequest.ortb2 || { + bcat: [], badv: [], - bcat: [] + wlang: [] }; const request = { @@ -118,10 +126,14 @@ export const spec = { device, source: {fd: 1}, tmax: bidderRequest.timeout, - bcat: ortb2.bcat, - badv: ortb2.badv, + bcat: ortb2.bcat || bidRequest.params.bcat || [], + badv: ortb2.badv || bidRequest.params.badv || [], + wlang: ortb2.wlang || bidRequest.params.wlang || [], user, - regs + regs, + ext: { + pageType: ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType + } }; const url = [END_POINT_URL, publisherId].join('/'); @@ -149,16 +161,45 @@ export const spec = { return bidResponses.map((bidResponse) => getBid(bids, currency, bidResponse)).filter(Boolean); }, + onBidWon: (bid) => { + if (bid.nurl) { + const resolvedNurl = replaceAuctionPrice(bid.nurl, bid.originalCpm); + ajax(resolvedNurl); + } + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = [] + const queryParams = []; + if (gdprConsent) { + queryParams.push(`gdpr=${Number(gdprConsent.gdprApplies && 1)}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`); + } + + if (uspConsent) { + queryParams.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + if (gppConsent) { + queryParams.push('gpp=' + encodeURIComponent(gppConsent)); + } + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: USER_SYNC_IMG_URL + (queryParams.length ? '?' + queryParams.join('&') : '') + }); + } + return syncs; + }, }; -function getSiteProperties({publisherId, bcat = []}, refererInfo) { +function getSiteProperties({publisherId}, refererInfo, ortb2) { const {getPageUrl, getReferrer} = internal; return { id: publisherId, name: publisherId, - domain: refererInfo?.domain || window.location?.host, - page: getPageUrl(refererInfo), - ref: getReferrer(refererInfo), + domain: ortb2?.site?.domain || refererInfo?.domain || window.location?.host, + page: ortb2?.site?.page || getPageUrl(refererInfo), + ref: ortb2?.site?.ref || getReferrer(refererInfo), publisher: { id: publisherId }, @@ -170,10 +211,10 @@ function getSiteProperties({publisherId, bcat = []}, refererInfo) { function getImps(validBidRequests) { return validBidRequests.map((bid, id) => { - const {tagId} = bid.params; + const {tagId, position} = bid.params; const imp = { id: id + 1, - banner: getBanners(bid), + banner: getBanners(bid, position), tagid: tagId } if (typeof bid.getFloor === 'function') { @@ -191,12 +232,18 @@ function getImps(validBidRequests) { imp.bidfloor = bidfloor; imp.bidfloorcur = bidfloorcur; } + imp['ext'] = { + gpid: deepAccess(bid, 'ortb2Imp.ext.gpid') + } return imp; }); } -function getBanners(bid) { - return getSizes(bid.sizes); +function getBanners(bid, pos) { + return { + ...getSizes(bid.sizes), + pos: pos + } } function getSizes(sizes) { @@ -232,7 +279,7 @@ function getBid(bids, currency, bidResponse) { return; } const { - price: cpm, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} + price: cpm, nurl, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} } = bidResponse; let requestId = bids[bidResponse.impid - 1].bidId; if (advertiserDomains && advertiserDomains.length > 0) { @@ -250,6 +297,7 @@ function getBid(bids, currency, bidResponse) { width, height, meta, + nurl, netRevenue: true }; } diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index bc775e78647..1977686dd23 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -157,7 +157,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { const sizes = getSizes(bidRequest); const bid = { requestId: serverBid.uuid, - cpm: rtbBid.cpm * MARGIN, + cpm: rtbBid.cpm / MARGIN, creativeId: rtbBid.creative_id, dealId: rtbBid.deal_id, currency: 'USD', diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md new file mode 100644 index 00000000000..b1bc3cb0d5b --- /dev/null +++ b/modules/topicsFpdModule.md @@ -0,0 +1,62 @@ +# Overview + +Module Name: topicsFpdModule + +# Description +Purpose of this module is to call the Topics API (document.browsingTopics()) which will fetch the first party domain as well third party domain(Iframe) topics data which will be sent onto user.data in bid stream. + +The intent of the Topics API is to provide callers (including third-party ad-tech or advertising providers on the page that run script) with coarse-grained advertising topics that the page visitor might currently be interested in. + +Topics Module(topicsFpdModule) should be included in prebid final package to call topics API. +Module topicsFpdModule helps to call the Topics API which will send topics data in bid stream (onto user.data) + +``` +try { + if ('browsingTopics' in document && document.featurePolicy.allowsFeature('browsing-topics')) { + topics = document.browsingTopics(); + } +} catch (e) { + console.error('Could not call topics API', e); +} +``` + +# Topics Iframe Configuration + +Topics iframe implementation is the enhancements of existing module under topicsFpdModule.js where different bidders will call the topic API under their domain to fetch the topics for respective domain and the segment data will be part of ORTB request under user.data object. Default config is maintained in the module itself. + +Below are the configuration which can be used to configure and override the default config maintained in the module. + +``` +pbjs.setConfig({ + userSync: { + ..., + topics: { + maxTopicCaller: 3, // SSP rotation + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html', + expiry: 7 // Configurable expiry days + },{ + bidder: 'rubicon', + iframeURL: 'https://rubicon.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + },{ + bidder: 'appnexus', + iframeURL: 'https://appnexus.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + }] + } + .... + } +}) +``` + +## Topics Config Descriptions + +| Field | Required? | Type | Description | +|---|---|---|---| +| topics.maxTopicCaller | no | integer | Defines the maximum numbers of Bidders Iframe which needs to be loaded on the publisher page. Default is 1 which is hardcoded in Module. Eg: topics.maxTopicCaller is set to 3. If there are 10 bidders configured along with their iframe URLS, random 3 bidders iframe URL is loaded which will call TOPICS API. If topics.maxTopicCaller is set to 0, it will load random 1(default) bidder iframe atleast. | +| topics.bidders | no | Array of objects | Array of topics callers with the iframe locations and other necessary informations like bidder(Bidder code) and expiry. Default Array of topics in the module itself.| +| topics.bidders[].bidder | yes | string | Bidder Code of the bidder(SSP). | +| topics.bidders[].iframeURL | yes | string | URL which is hosted on bidder/SSP/third-party domains which will call Topics API. | +| topics.bidders[].expiry | no | integer | Max number of days where Topics data will be persist. If Data is stored for more than mentioned expiry day, it will be deleted from storage. Default is 21 days which is hardcoded in Module. | \ No newline at end of file diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 7f6ec90c7b9..3ce44d067bf 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -154,6 +154,10 @@ function _buildPostBody(bidRequests, bidderRequest) { if (!isEmpty(ext)) { data.ext = ext; } + + if (bidderRequest?.ortb2?.regs?.gpp) { + data.regs = Object.assign({}, bidderRequest.ortb2.regs); + } return data; } diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index bb3e4abd838..3f3bb66d8ae 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,8 +1,9 @@ -import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { bidderSettings } from '../src/bidderSettings.js'; +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; +import { config } from '../src/config.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; @@ -26,11 +27,12 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { - const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -58,10 +60,23 @@ function isBidRequestValid(bid) { return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; - const { ext } = params; - let { bidFloor } = params; +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); const uniqueDealId = getUniqueDealId(hashUrl); @@ -110,7 +125,15 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { isStorageAllowed: isStorageAllowed, gpid: gpid, cat: cat, - pagecat: pagecat + pagecat: pagecat, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout, + webSessionId: webSessionId }; appendUserIdsToRequestPayload(data, userId); @@ -127,6 +150,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { data.usPrivacy = bidderRequest.uspConsent; } + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -169,10 +200,11 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { // TODO: does the fallback make sense here? const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); requests.push(request); }); return requests; @@ -182,14 +214,14 @@ function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { return []; } - const { bidId } = request.data; - const { results } = serverResponse.body; + const {bidId} = request.data; + const {results} = serverResponse.body; let output = []; try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; + const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -228,8 +260,8 @@ function interpretResponse(serverResponse, request) { function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { let syncs = []; - const { iframeEnabled, pixelEnabled } = syncOptions; - const { gdprApplies, consentString = '' } = gdprConsent; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` @@ -253,7 +285,9 @@ export function hashCode(s, prefix = '_') { let h = 0 let i = 0; if (l > 0) { - while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } } return prefix + h; } @@ -310,7 +344,8 @@ export function getCacheOpt() { export function getStorageItem(key) { try { return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { } + } catch (e) { + } return null; } @@ -318,9 +353,10 @@ export function getStorageItem(key) { export function setStorageItem(key, value, timestamp) { try { const created = timestamp || Date.now(); - const data = JSON.stringify({ value, created }); + const data = JSON.stringify({value, created}); storage.setDataInLocalStorage(key, data); - } catch (e) { } + } catch (e) { + } } export function tryParseJSON(value) { diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index 8f9ede4fccc..9dd635a990f 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -279,6 +279,11 @@ function generateOpenRtbObject(bidderRequest, bid) { outBoundBidRequest.site.id = bid.params.dcn; }; + if (bidderRequest.ortb2?.regs?.gpp) { + outBoundBidRequest.regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + outBoundBidRequest.regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid + }; + if (bidderRequest.ortb2) { outBoundBidRequest = appendFirstPartyData(outBoundBidRequest, bid); }; diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 88e1494d416..cadeb9c1300 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,17 +1,17 @@ -import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { find } from '../src/polyfill.js' -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' -import { Renderer } from '../src/Renderer.js' +import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { find } from '../src/polyfill.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const ENDPOINT = 'https://ad.yieldlab.net' -const BIDDER_CODE = 'yieldlab' -const BID_RESPONSE_TTL_SEC = 300 -const CURRENCY_CODE = 'EUR' -const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' -const GVLID = 70 -const DIMENSION_SIGN = 'x' +const ENDPOINT = 'https://ad.yieldlab.net'; +const BIDDER_CODE = 'yieldlab'; +const BID_RESPONSE_TTL_SEC = 300; +const CURRENCY_CODE = 'EUR'; +const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event'; +const GVLID = 70; +const DIMENSION_SIGN = 'x'; export const spec = { code: BIDDER_CODE, @@ -22,11 +22,11 @@ export const spec = { * @param {object} bid * @returns {boolean} */ - isBidRequestValid: function (bid) { + isBidRequestValid(bid) { if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { - return true + return true; } - return false + return false; }, /** @@ -35,84 +35,85 @@ export const spec = { * @param [bidderRequest] * @returns {ServerRequest|ServerRequest[]} */ - buildRequests: function (validBidRequests, bidderRequest) { + buildRequests(validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const adslotIds = [] + const adslotIds = []; const adslotSizes = []; const adslotFloors = []; - const timestamp = Date.now() + const timestamp = Date.now(); const query = { ts: timestamp, - json: true - } + json: true, + }; _each(validBidRequests, function (bid) { - adslotIds.push(bid.params.adslotId) - const sizes = extractSizes(bid) + adslotIds.push(bid.params.adslotId); + const sizes = extractSizes(bid); if (sizes.length > 0) { - adslotSizes.push(bid.params.adslotId + ':' + sizes.join('|')) + adslotSizes.push(bid.params.adslotId + ':' + sizes.join('|')); } if (bid.params.extId) { query.id = bid.params.extId; } if (bid.params.targeting) { - query.t = createTargetingString(bid.params.targeting) + query.t = createTargetingString(bid.params.targeting); } if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { - query.ids = createUserIdString(bid.userIdAsEids) + query.ids = createUserIdString(bid.userIdAsEids); + query.atypes = createUserIdAtypesString(bid.userIdAsEids); } if (bid.params.customParams && isPlainObject(bid.params.customParams)) { for (const prop in bid.params.customParams) { - query[prop] = bid.params.customParams[prop] + query[prop] = bid.params.customParams[prop]; } } if (bid.schain && isPlainObject(bid.schain) && Array.isArray(bid.schain.nodes)) { - query.schain = createSchainString(bid.schain) + query.schain = createSchainString(bid.schain); } - const iabContent = getContentObject(bid) + const iabContent = getContentObject(bid); if (iabContent) { - query.iab_content = createIabContentString(iabContent) + query.iab_content = createIabContentString(iabContent); } - const floor = getBidFloor(bid, sizes) + const floor = getBidFloor(bid, sizes); if (floor) { adslotFloors.push(bid.params.adslotId + ':' + floor); } - }) + }); if (bidderRequest) { if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { // TODO: is 'page' the right value here? - query.pubref = bidderRequest.refererInfo.page + query.pubref = bidderRequest.refererInfo.page; } if (bidderRequest.gdprConsent) { - query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true; if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString + query.consent = bidderRequest.gdprConsent.consentString; } } } - const adslots = adslotIds.join(',') + const adslots = adslotIds.join(','); if (adslotSizes.length > 0) { - query.sizes = adslotSizes.join(',') + query.sizes = adslotSizes.join(','); } if (adslotFloors.length > 0) { - query.floor = adslotFloors.join(',') + query.floor = adslotFloors.join(','); } - const queryString = createQueryString(query) + const queryString = createQueryString(query); return { method: 'GET', url: `${ENDPOINT}/yp/${adslots}?${queryString}`, validBidRequests: validBidRequests, - queryParams: query - } + queryParams: query, + }; }, /** @@ -121,29 +122,29 @@ export const spec = { * @param {BidRequest} originalBidRequest * @returns {Bid[]} */ - interpretResponse: function (serverResponse, originalBidRequest) { - const bidResponses = [] - const timestamp = Date.now() - const reqParams = originalBidRequest.queryParams + interpretResponse(serverResponse, originalBidRequest) { + const bidResponses = []; + const timestamp = Date.now(); + const reqParams = originalBidRequest.queryParams; originalBidRequest.validBidRequests.forEach(function (bidRequest) { if (!serverResponse.body) { - return + return; } const matchedBid = find(serverResponse.body, function (bidResponse) { - return bidRequest.params.adslotId == bidResponse.id - }) + return bidRequest.params.adslotId == bidResponse.id; + }); if (matchedBid) { - const adUnitSize = bidRequest.sizes.length === 2 && !isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] - const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize - const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : '' - const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : '' - const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : '' - const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : '' - const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : '' - const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : '' + const adUnitSize = bidRequest.sizes.length === 2 && !isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0]; + const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize; + const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : ''; + const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : ''; + const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : ''; + const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : ''; + const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : ''; + const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : ''; const bidResponse = { requestId: bidRequest.bidId, @@ -158,38 +159,38 @@ export const spec = { referrer: '', ad: ``, meta: { - advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a' - } - } + advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a', + }, + }; if (isVideo(bidRequest, adType)) { - const playersize = getPlayerSize(bidRequest) + const playersize = getPlayerSize(bidRequest); if (playersize) { - bidResponse.width = playersize[0] - bidResponse.height = playersize[1] + bidResponse.width = playersize[0]; + bidResponse.height = playersize[1]; } - bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}` + bidResponse.mediaType = VIDEO; + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}`; if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, url: OUTSTREAMPLAYER_URL, - loaded: false - }) - renderer.setRender(outstreamRender) - bidResponse.renderer = renderer + loaded: false, + }); + renderer.setRender(outstreamRender); + bidResponse.renderer = renderer; } } if (isNative(bidRequest, adType)) { // there may be publishers still rely on it - const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` - bidResponse.adUrl = url - bidResponse.mediaType = NATIVE - const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2) + const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}`; + bidResponse.adUrl = url; + bidResponse.mediaType = NATIVE; + const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2); const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : { url: '', w: 0, h: 0 }; - const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1) - const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3) + const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1); + const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3); bidResponse.native = { title: nativeTitleAsset ? nativeTitleAsset.title.text : '', body: nativeBodyAsset ? nativeBodyAsset.data.value : '', @@ -203,10 +204,10 @@ export const spec = { }; } - bidResponses.push(bidResponse) + bidResponses.push(bidResponse); } - }) - return bidResponses + }); + return bidResponses; }, /** @@ -218,13 +219,13 @@ export const spec = { * @param {string} uspConsent Is the US Privacy Consent string. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; if (syncOptions.iframeEnabled) { const params = []; params.push(`ts=${timestamp()}`); - params.push(`type=h`) + params.push(`type=h`); if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); } @@ -233,12 +234,12 @@ export const spec = { } syncs.push({ type: 'iframe', - url: `${ENDPOINT}/d/6846326/766/2x2?${params.join('&')}` + url: `${ENDPOINT}/d/6846326/766/2x2?${params.join('&')}`, }); } return syncs; - } + }, }; /** @@ -248,7 +249,7 @@ export const spec = { * @returns {Boolean} */ function isVideo(format, adtype) { - return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' + return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video'; } /** @@ -258,7 +259,7 @@ function isVideo(format, adtype) { * @returns {Boolean} */ function isNative(format, adtype) { - return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native' + return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native'; } /** @@ -267,8 +268,8 @@ function isNative(format, adtype) { * @returns {Boolean} */ function isOutstream(format) { - const context = deepAccess(format, 'mediaTypes.video.context') - return (context === 'outstream') + const context = deepAccess(format, 'mediaTypes.video.context'); + return (context === 'outstream'); } /** @@ -277,30 +278,45 @@ function isOutstream(format) { * @returns {Array} */ function getPlayerSize(format) { - const playerSize = deepAccess(format, 'mediaTypes.video.playerSize') - return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize + const playerSize = deepAccess(format, 'mediaTypes.video.playerSize'); + return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize; } /** - * Expands a 'WxH' string as a 2-element [W, H] array + * Expands a 'WxH' string to a 2-element [W, H] array * @param {String} size * @returns {Array} */ function parseSize(size) { - return size.split(DIMENSION_SIGN).map(Number) + return size.split(DIMENSION_SIGN).map(Number); } /** * Creates a string out of an array of eids with source and uid - * @param {Array} eids + * @param {Array.<{source: String, uids: Array.<{id: String, atype: Number, ext: Object}>}>} eids * @returns {String} */ function createUserIdString(eids) { - const str = [] + const str = []; for (let i = 0; i < eids.length; i++) { - str.push(eids[i].source + ':' + eids[i].uids[0].id) + str.push(eids[i].source + ':' + eids[i].uids[0].id); } - return str.join(',') + return str.join(','); +} + +/** + * Creates a string from an array of eids with ID provider and atype if atype exists + * @param {Array.<{source: String, uids: Array.<{id: String, atype: Number, ext: Object}>}>} eids + * @returns {String} idprovider:atype,idprovider2:atype2,... + */ +function createUserIdAtypesString(eids) { + const str = []; + for (let i = 0; i < eids.length; i++) { + if (eids[i].uids[0].atype) { + str.push(eids[i].source + ':' + eids[i].uids[0].atype); + } + } + return str.join(','); } /** @@ -309,18 +325,18 @@ function createUserIdString(eids) { * @returns {String} */ function createQueryString(obj) { - const str = [] + const str = []; for (const p in obj) { if (obj.hasOwnProperty(p)) { - const val = obj[p] + const val = obj[p]; if (p !== 'schain' && p !== 'iab_content') { - str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)) + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)); } else { - str.push(p + '=' + val) + str.push(p + '=' + val); } } } - return str.join('&') + return str.join('&'); } /** @@ -329,15 +345,15 @@ function createQueryString(obj) { * @returns {String} */ function createTargetingString(obj) { - const str = [] + const str = []; for (const p in obj) { if (obj.hasOwnProperty(p)) { - const key = p - const val = obj[p] - str.push(key + '=' + val) + const key = p; + const val = obj[p]; + str.push(key + '=' + val); } } - return str.join('&') + return str.join('&'); } /** @@ -346,13 +362,13 @@ function createTargetingString(obj) { * @returns {String} */ function createSchainString(schain) { - const ver = schain.ver || '' - const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : '' - const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'] + const ver = schain.ver || ''; + const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : ''; + const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext']; const nodesString = schain.nodes.reduce((acc, node) => { - return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}` - }, '') - return `${ver},${complete}${nodesString}` + return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}`; + }, ''); + return `${ver},${complete}${nodesString}`; } /** @@ -364,15 +380,15 @@ function createSchainString(schain) { */ function getContentObject(bid) { if (bid.params.iabContent && isPlainObject(bid.params.iabContent)) { - return bid.params.iabContent + return bid.params.iabContent; } const globalContent = deepAccess(bid, 'ortb2.site') ? deepAccess(bid, 'ortb2.site.content') - : deepAccess(bid, 'ortb2.app.content') + : deepAccess(bid, 'ortb2.app.content'); if (globalContent && isPlainObject(globalContent)) { - return globalContent + return globalContent; } - return undefined + return undefined; } /** @@ -385,15 +401,15 @@ function getContentObject(bid) { * @returns {String} */ function createIabContentString(iabContent) { - const arrKeys = ['keywords', 'cat'] - const str = [] + const arrKeys = ['keywords', 'cat']; + const str = []; const transformObjToParam = (obj = {}, extraKey = '') => { for (const key in obj) { if ((arrKeys.indexOf(key) !== -1 && Array.isArray(obj[key]))) { // Array of defined keyword which have to be joined into one value from "key: [value1, value2, value3]" to "key:value1|value2|value3" - str.push(''.concat(key, ':', obj[key].map(node => encodeURIComponent(node)).join('|'))) + str.push(''.concat(key, ':', obj[key].map(node => encodeURIComponent(node)).join('|'))); } else if (typeof obj[key] !== 'object') { - str.push(''.concat(extraKey + key, ':', encodeURIComponent(obj[key]))) + str.push(''.concat(extraKey + key, ':', encodeURIComponent(obj[key]))); } else { // Object has to be further flattened transformObjToParam(obj[key], ''.concat(extraKey, key, '.')); @@ -401,7 +417,7 @@ function createIabContentString(iabContent) { } return str.join(','); }; - return encodeURIComponent(transformObjToParam(iabContent)) + return encodeURIComponent(transformObjToParam(iabContent)); } /** @@ -410,7 +426,7 @@ function createIabContentString(iabContent) { * @returns {String} */ function encodeURIComponentWithBangIncluded(str) { - return encodeURIComponent(str).replace(/!/g, '%21') + return encodeURIComponent(str).replace(/!/g, '%21'); } /** @@ -419,11 +435,11 @@ function encodeURIComponentWithBangIncluded(str) { */ function outstreamRender(bid) { bid.renderer.push(() => { - window.ma_width = bid.width - window.ma_height = bid.height - window.ma_vastUrl = bid.vastUrl - window.ma_container = bid.adUnitCode - window.document.dispatchEvent(new Event('ma-start-event')) + window.ma_width = bid.width; + window.ma_height = bid.height; + window.ma_vastUrl = bid.vastUrl; + window.ma_container = bid.adUnitCode; + window.document.dispatchEvent(new Event('ma-start-event')); }); } @@ -434,33 +450,33 @@ function outstreamRender(bid) { * @returns {string[]} */ function extractSizes(bid) { - const { mediaTypes } = bid // see https://docs.prebid.org/dev-docs/adunit-reference.html#examples - const sizes = [] + const { mediaTypes } = bid; // see https://docs.prebid.org/dev-docs/adunit-reference.html#examples + const sizes = []; if (isPlainObject(mediaTypes)) { - const { [BANNER]: bannerType } = mediaTypes + const { [BANNER]: bannerType } = mediaTypes; // only applies for multi size Adslots -> BANNER if (bannerType && isArray(bannerType.sizes)) { if (isArray(bannerType.sizes[0])) { // multiple sizes given - sizes.push(bannerType.sizes) + sizes.push(bannerType.sizes); } else { // just one size provided as array -> wrap to uniformly flatten later - sizes.push([bannerType.sizes]) + sizes.push([bannerType.sizes]); } } // The bid top level field `sizes` is deprecated and should not be used anymore. Keeping it for compatibility. } else if (isArray(bid.sizes)) { if (isArray(bid.sizes[0])) { - sizes.push(bid.sizes) + sizes.push(bid.sizes); } else { - sizes.push([bid.sizes]) + sizes.push([bid.sizes]); } } /** @type {Set} */ - const deduplicatedSizeStrings = new Set(sizes.flat().map(([width, height]) => width + DIMENSION_SIGN + height)) + const deduplicatedSizeStrings = new Set(sizes.flat().map(([width, height]) => width + DIMENSION_SIGN + height)); - return Array.from(deduplicatedSizeStrings) + return Array.from(deduplicatedSizeStrings); } /** @@ -481,7 +497,7 @@ function getBidFloor(bid, sizes) { const floor = bid.getFloor({ currency: CURRENCY_CODE, mediaType: mediaType !== undefined && spec.supportedMediaTypes.includes(mediaType) ? mediaType : '*', - size: sizes.length !== 1 ? '*' : sizes[0].split(DIMENSION_SIGN) + size: sizes.length !== 1 ? '*' : sizes[0].split(DIMENSION_SIGN), }); if (floor.currency === CURRENCY_CODE) { return (floor.floor * 100).toFixed(0); @@ -489,4 +505,4 @@ function getBidFloor(bid, sizes) { return undefined; } -registerBidder(spec) +registerBidder(spec); diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 64ab8a87eea..5a0f302ab34 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -385,7 +385,7 @@ function openRtbRequest(bidRequests, bidderRequest) { at: 1, imp: bidRequests.map(bidRequest => openRtbImpression(bidRequest)), site: openRtbSite(bidRequests[0], bidderRequest), - device: openRtbDevice(bidRequests[0]), + device: deepAccess(bidderRequest, 'ortb2.device'), badv: bidRequests[0].params.badv || [], bcat: deepAccess(bidderRequest, 'bcat') || bidRequests[0].params.bcat || [], ext: { @@ -507,17 +507,6 @@ function openRtbSite(bidRequest, bidderRequest) { return result; } -/** - * @return Object OpenRTB's 'device' object - */ -function openRtbDevice(bidRequest) { - const deviceObj = { - ua: navigator.userAgent, - language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - }; - return deviceObj; -} - /** * Updates openRtbRequest with GDPR info from bidderRequest, if present. * @param {Object} openRtbRequest OpenRTB's request to update. diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 6c5b9783782..74ddfc0bd06 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -90,6 +90,21 @@ export const spec = { if (!impData.banner && !impData.video) { impData.banner = buildBanner(request); } + + if (typeof request.getFloor === 'function') { + const floorInfo = request.getFloor({ + currency: 'USD', + mediaType: impData.video ? 'video' : 'banner', + size: [ impData.video ? impData.video.w : impData.banner.w, impData.video ? impData.video.h : impData.banner.h ] + }); + if (floorInfo && floorInfo.floor) { + impData.bidfloor = floorInfo.floor; + } + } + if (!impData.bidfloor && params.bidfloor) { + impData.bidfloor = params.bidfloor; + } + return impData; }); diff --git a/modules/zeusPrimeRtdProvider.js b/modules/zeusPrimeRtdProvider.js index 28c46957c50..2a455a14f7b 100644 --- a/modules/zeusPrimeRtdProvider.js +++ b/modules/zeusPrimeRtdProvider.js @@ -1,328 +1,8 @@ -/** - * This module adds Zeus Insights For Publishers (ZIP) provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * - * This module will request the article topics for the current page and add them as page keyvalues - * for the ad requests. - * - * @module modules/zeusInsightsForPublishersRtdProvider - * @requires module:modules/realTimeData - */ - -import { logInfo, logError, logWarn, logMessage } from '../src/utils.js' +import { logWarn } from '../src/utils.js' import { submodule } from '../src/hook.js' -import { ajaxBuilder } from '../src/ajax.js' - -class Logger { - get showDebug() { - if (this._showDebug === true || this._showDebug === false) { - return this._showDebug - } - - return window.zeusPrime?.debug || false - } - set showDebug(shouldShow) { - this._showDebug = shouldShow - } - - get error() { - return logError.bind(this, 'zeusPrimeRtdProvider: ') - } - get warn() { - return logWarn.bind(this, 'zeusPrimeRtdProvider: ') - } - get info() { - return logInfo.bind(this, 'zeusPrimeRtdProvider: ') - } - get debug() { - if (this.showDebug) { - return logMessage.bind(this, 'zeusPrimeRtdProvider: ') - } - - return () => {} - } -} - -var logger = new Logger() - -function loadCommandQueue() { - window.zeusPrime = window.zeusPrime || { cmd: [] } - const queue = [...window.zeusPrime.cmd] - - window.zeusPrime.cmd = [] - window.zeusPrime.cmd.push = (callback) => { - callback(window.zeusPrime) - } - - queue.forEach((callback) => callback(window.zeusPrime)) -} - -function markStatusComplete(key) { - const status = window?.zeusPrime?.status - if (status) { - status[key] = true - } -} - -function createStatus() { - if (window.zeusPrime && !window.zeusPrime.status) { - Object.defineProperty(window.zeusPrime, 'status', { - enumerable: false, - value: { - initComplete: false, - primeKeyValueSet: false, - insightsReqSent: false, - insightsReqReceived: false, - insightsKeyValueSet: false, - scriptComplete: false, - }, - }) - } -} - -function loadPrimeQueryParams() { - try { - const params = new URLSearchParams(window.location.search) - params.forEach((paramValue, paramKey) => { - if (!paramKey.startsWith('zeus_prime_')) { - return - } - - let key = paramKey.replace('zeus_prime_', '') - let value = paramValue.toLowerCase() - - if (value === 'true' || value === '1') { - value = true - } else if (value === 'false' || value === '0') { - value = false - } - - window.zeusPrime[key] = value - }) - } catch (_) {} -} - -const DEFAULT_API = 'https://insights.zeustechnology.com' - -function init(gamId = null, options = {}) { - window.zeusPrime = window.zeusPrime || { cmd: [] } - - window.zeusPrime.gamId = gamId || options.gamId || window.zeusPrime.gamId || undefined - window.zeusPrime.api = DEFAULT_API - window.zeusPrime.hostname = options.hostname || window.location?.hostname || '' - window.zeusPrime.pathname = options.pathname || window.location?.pathname || '' - window.zeusPrime.pageUrl = `${window.zeusPrime.hostname}${window.zeusPrime.pathname}` - window.zeusPrime.pageHash = options.pageHash || null - window.zeusPrime.debug = window.zeusPrime.debug || options.debug === true || false - window.zeusPrime.disabled = window.zeusPrime.disabled || options.disabled === true || false - - loadPrimeQueryParams() - - logger.showDebug = window.zeusPrime.debug - - createStatus() - markStatusComplete('initComplete') -} - -function setTargeting() { - const { gamId, hostname } = window.zeusPrime - - if (typeof gamId !== 'string') { - throw new Error(`window.zeusPrime.gamId must be a string. Received: ${String(gamId)}`) - } - - addKeyValueToGoogletag(`zeus_${gamId}`, hostname) - logger.debug(`Setting zeus_${gamId}=${hostname}`) - markStatusComplete('primeKeyValueSet') -} -function setPrimeAsDisabled() { - addKeyValueToGoogletag('zeus_prime', 'false') - logger.debug('Disabling prime; Setting key-value zeus_prime to false') -} - -function addKeyValueToGoogletag(key, value) { - window.googletag = window.googletag || { cmd: [] } - window.googletag.cmd.push(function () { - window.googletag.pubads().setTargeting(key, value) - }) -} - -function isInsightsPage(pathname = '') { - const NOT_SECTIONS = [ - { - test: /\/search/, - type: 'search', - }, - { - test: /\/author/, - type: 'author', - }, - { - test: /\/event/, - type: 'event', - }, - { - test: /\/homepage/, - type: 'front', - }, - { - test: /^\/?$/, - type: 'front', - }, - ] - - const typeObj = NOT_SECTIONS.find((pg) => pathname.match(pg.test)) - return typeObj === undefined -} - -async function getUrlHash(canonical) { - try { - const buf = await window.crypto.subtle.digest( - 'SHA-1', - new TextEncoder('utf-8').encode(canonical) - ) - const hashed = Array.prototype.map - .call(new Uint8Array(buf), (x) => `00${x.toString(16)}`.slice(-2)) - .join('') - - return hashed - } catch (e) { - logger.error('Failed to load hash', e.message) - logger.debug('Exception', e) - return '' - } -} - -async function sendPrebidRequest(url) { - return new Promise((resolve, reject) => { - const ajax = ajaxBuilder() - ajax(url, { - success: (responseText, response) => { - resolve({ - ...response, - status: response.status, - json: () => JSON.parse(responseText), - }) - }, - - error: (responseText, response) => { - if (!response.status) { - reject(response) - } - - let json = responseText - if (responseText) { - try { - json = JSON.parse(responseText) - } catch (_) { - json = null - } - } - - resolve({ - status: response.status, - json: () => json || null, - responseValue: json, - }) - }, - }) - }) -} - -async function requestTopics() { - const { api, hostname, pageUrl } = window.zeusPrime - - if (!window.zeusPrime.pageHash) { - window.zeusPrime.pageHash = await getUrlHash(pageUrl) - } - - const pageHash = window.zeusPrime.pageHash - const zeusInsightsUrl = `${api}/${hostname}/${pageHash}?article_location=${pageUrl}` - - logger.debug('Requesting topics', zeusInsightsUrl) - try { - markStatusComplete('insightsReqSent') - const response = await sendPrebidRequest(zeusInsightsUrl) - if (response.status === 200) { - logger.debug('topics found') - markStatusComplete('insightsReqReceived') - return await response.json() - } else if ( - response.status === 204 || - response.status < 200 || - (response.status >= 300 && response.status <= 399) - ) { - logger.debug('no topics found') - markStatusComplete('insightsReqReceived') - return null - } else { - logger.error(`Topics request returned error: ${response.status}`) - markStatusComplete('insightsReqReceived') - return null - } - } catch (e) { - logger.error('failed to request topics', e) - return null - } -} - -function setTopicsTargeting(topics = []) { - if (topics.length === 0) { - return - } - - window.googletag = window.googletag || { cmd: [] } - window.googletag.cmd.push(function () { - window.googletag.pubads().setTargeting('zeus_insights', topics) - }) - - markStatusComplete('insightsKeyValueSet') -} - -async function startTopicsRequest() { - if (isInsightsPage(window.zeusPrime.pathname)) { - const response = await requestTopics() - if (response) { - setTopicsTargeting(response?.topics) - } - } else { - logger.debug('This page is not eligible for topics, request will be skipped') - } -} - -async function run(gamId, options = {}) { - logger.showDebug = options.debug || false - - try { - init(gamId, options) - loadCommandQueue() - - if (window.zeusPrime.disabled) { - setPrimeAsDisabled() - } else { - setTargeting() - await startTopicsRequest() - } - } catch (e) { - logger.error('Failed to run.', e.message || e) - } finally { - markStatusComplete('scriptComplete') - } -} - -/** - * @preserve - * Initializes the ZeusPrime RTD Submodule. The config provides the GamID for this - * site that is used to configure Prime. - * @param {object} config The Prebid configuration for this module. - * @param {object} config.params The parameters for this module. - * @param {string} config.params.gamId The Gam ID (or Network Code) in GAM for this site. - */ -function initModule(config) { - const { params } = config || {} - const { gamId, ...rest } = params || {} - run(gamId, rest) +function initModule() { + logWarn('Zeus Prime has been deprecated. This module will be removed in Prebid 8.') } /** diff --git a/modules/zeusPrimeRtdProvider.md b/modules/zeusPrimeRtdProvider.md index f3a6c5018d5..40b44e76f1c 100644 --- a/modules/zeusPrimeRtdProvider.md +++ b/modules/zeusPrimeRtdProvider.md @@ -1,5 +1,8 @@ # Overview +# NOTE: ZEUS PRIME HAS BEEN DEPRECATED! +# THIS MODULE WILL BE REMOVED IN PREBID 8. + Module Name: Zeus Prime RTD Provider Module Type: Rtd Provider Maintainer: support@zeustechnology.com diff --git a/package-lock.json b/package-lock.json index c903795e59d..dca60dbf37e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "7.30.0-pre", + "version": "7.36.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "7.28.0-pre", + "version": "7.36.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.1" + "live-connect-js": "^4.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -2797,9 +2797,9 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true, "funding": [ { @@ -8453,9 +8453,9 @@ } }, "node_modules/devtools/node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true, "funding": [ { @@ -8534,9 +8534,9 @@ } }, "node_modules/documentation": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", - "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.1.tgz", + "integrity": "sha512-Y/brACCE3sNnDJPFiWlhXrqGY+NelLYVZShLGse5bT1KdohP4JkPf5T2KNq1YWhIEbDYl/1tebRLC0WYbPQxVw==", "dev": true, "dependencies": { "@babel/core": "^7.18.10", @@ -8548,7 +8548,7 @@ "chokidar": "^3.5.3", "diff": "^5.1.0", "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^12.0.0", + "git-url-parse": "^13.1.0", "github-slugger": "1.4.0", "glob": "^8.0.3", "globals-docs": "^2.4.1", @@ -11261,22 +11261,22 @@ } }, "node_modules/git-up": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", - "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", "dev": true, "dependencies": { "is-ssh": "^1.4.0", - "parse-url": "^7.0.2" + "parse-url": "^8.1.0" } }, "node_modules/git-url-parse": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", - "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", "dev": true, "dependencies": { - "git-up": "^6.0.0" + "git-up": "^7.0.0" } }, "node_modules/github-slugger": { @@ -13818,9 +13818,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "node_modules/http-errors": { @@ -16229,9 +16229,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.1.tgz", - "integrity": "sha512-rwB0IQfuKPJM+I96nLyq8Utr3LkQ7Z/iuq/xKlWDckQRJLYyWkk7F7yaavf/VsjazzLK2dpJeXGijoDkK4Vz8g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", + "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -19420,24 +19420,21 @@ } }, "node_modules/parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dev": true, "dependencies": { "protocols": "^2.0.0" } }, "node_modules/parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, "dependencies": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" + "parse-path": "^7.0.0" } }, "node_modules/parseurl": { @@ -22965,9 +22962,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", - "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true, "funding": [ { @@ -27308,9 +27305,9 @@ } }, "ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true }, "uuid": { @@ -31721,9 +31718,9 @@ } }, "ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true }, "uuid": { @@ -31782,9 +31779,9 @@ } }, "documentation": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", - "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.1.tgz", + "integrity": "sha512-Y/brACCE3sNnDJPFiWlhXrqGY+NelLYVZShLGse5bT1KdohP4JkPf5T2KNq1YWhIEbDYl/1tebRLC0WYbPQxVw==", "dev": true, "requires": { "@babel/core": "^7.18.10", @@ -31797,7 +31794,7 @@ "chokidar": "^3.5.3", "diff": "^5.1.0", "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^12.0.0", + "git-url-parse": "^13.1.0", "github-slugger": "1.4.0", "glob": "^8.0.3", "globals-docs": "^2.4.1", @@ -33988,22 +33985,22 @@ } }, "git-up": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", - "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", "dev": true, "requires": { "is-ssh": "^1.4.0", - "parse-url": "^7.0.2" + "parse-url": "^8.1.0" } }, "git-url-parse": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", - "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", "dev": true, "requires": { - "git-up": "^6.0.0" + "git-up": "^7.0.0" } }, "github-slugger": { @@ -36048,9 +36045,9 @@ "dev": true }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "http-errors": { @@ -37868,9 +37865,9 @@ "dev": true }, "live-connect-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.1.tgz", - "integrity": "sha512-rwB0IQfuKPJM+I96nLyq8Utr3LkQ7Z/iuq/xKlWDckQRJLYyWkk7F7yaavf/VsjazzLK2dpJeXGijoDkK4Vz8g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", + "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", "requires": { "tiny-hashes": "1.0.1" } @@ -40244,24 +40241,21 @@ "dev": true }, "parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dev": true, "requires": { "protocols": "^2.0.0" } }, "parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, "requires": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" + "parse-path": "^7.0.0" } }, "parseurl": { @@ -43064,9 +43058,9 @@ } }, "ua-parser-js": { - "version": "0.7.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", - "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 490303874f7..7163c732634 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.30.0-pre", + "version": "7.36.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -130,7 +130,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.1" + "live-connect-js": "^4.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/src/adapterManager.js b/src/adapterManager.js index eb6a74a82a4..f4f9e59fb84 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -31,7 +31,7 @@ import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; import {adunitCounter} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; -import {GdprConsentHandler, UspConsentHandler} from './consentHandler.js'; +import {GdprConsentHandler, UspConsentHandler, GppConsentHandler} from './consentHandler.js'; import * as events from './events.js'; import CONSTANTS from './constants.json'; import {useMetrics} from './utils/perfMetrics.js'; @@ -161,6 +161,7 @@ function getAdUnitCopyForClientAdapters(adUnits) { export let gdprDataHandler = new GdprConsentHandler(); export let uspDataHandler = new UspConsentHandler(); +export let gppDataHandler = new GppConsentHandler(); export let coppaDataHandler = { getCoppa: function() { @@ -309,17 +310,17 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } }); - if (gdprDataHandler.getConsentData()) { - bidRequests.forEach(bidRequest => { + bidRequests.forEach(bidRequest => { + if (gdprDataHandler.getConsentData()) { bidRequest['gdprConsent'] = gdprDataHandler.getConsentData(); - }); - } - - if (uspDataHandler.getConsentData()) { - bidRequests.forEach(bidRequest => { + } + if (uspDataHandler.getConsentData()) { bidRequest['uspConsent'] = uspDataHandler.getConsentData(); - }); - } + } + if (gppDataHandler.getConsentData()) { + bidRequest['gppConsent'] = gppDataHandler.getConsentData(); + } + }); bidRequests.forEach(bidRequest => { config.runWithBidder(bidRequest.bidderCode, () => { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 7e66b849783..55c84e57062 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -214,7 +214,7 @@ export function newBidder(spec) { done(); config.runWithBidder(spec.code, () => { events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); - registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent); + registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent, bidderRequest.gppConsent); }); } @@ -295,8 +295,8 @@ export function newBidder(spec) { return false; } - function registerSyncs(responses, gdprConsent, uspConsent) { - registerSyncInner(spec, responses, gdprConsent, uspConsent); + function registerSyncs(responses, gdprConsent, uspConsent, gppConsent) { + registerSyncInner(spec, responses, gdprConsent, uspConsent, gppConsent); } function filterAndWarn(bid) { @@ -448,14 +448,14 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe }) }, 'processBidderRequests') -export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent) { +export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent, gppConsent) { const aliasSyncEnabled = config.getConfig('userSync.aliasSyncEnabled'); if (spec.getUserSyncs && (aliasSyncEnabled || !adapterManager.aliasRegistry[spec.code])) { let filterConfig = config.getConfig('userSync.filterSettings'); let syncs = spec.getUserSyncs({ iframeEnabled: !!(filterConfig && (filterConfig.iframe || filterConfig.all)), pixelEnabled: !!(filterConfig && (filterConfig.image || filterConfig.all)), - }, responses, gdprConsent, uspConsent); + }, responses, gdprConsent, uspConsent, gppConsent); if (syncs) { if (!Array.isArray(syncs)) { syncs = [syncs]; diff --git a/src/adloader.js b/src/adloader.js index 64408683e9f..f0b7f7f3e8c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -20,7 +20,9 @@ const _approvedLoadExternalJSList = [ 'hadron', 'medianet', 'improvedigital', - 'aaxBlockmeter' + 'aaxBlockmeter', + 'confiant', + 'arcspan' ] /** diff --git a/src/auction.js b/src/auction.js index 87397b0dc15..41e6fe3565b 100644 --- a/src/auction.js +++ b/src/auction.js @@ -93,6 +93,7 @@ import CONSTANTS from './constants.json'; import {GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {createBid} from './bidfactory.js'; +import {adjustCpm} from './utils/cpm.js'; const { syncUsers } = userSync; @@ -971,17 +972,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { } export function adjustBids(bid) { - let code = bid.bidderCode; - let bidPriceAdjusted = bid.cpm; - const bidCpmAdjustment = bidderSettings.get(code || null, 'bidCpmAdjustment'); - - if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { - try { - bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); - } catch (e) { - logError('Error during bid adjustment', 'bidmanager.js', e); - } - } + let bidPriceAdjusted = adjustCpm(bid.cpm, bid); if (bidPriceAdjusted >= 0) { bid.cpm = bidPriceAdjusted; diff --git a/src/consentHandler.js b/src/consentHandler.js index 01470a4b38c..b1b2a04c043 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -107,3 +107,14 @@ export class GdprConsentHandler extends ConsentHandler { } } } + +export class GppConsentHandler extends ConsentHandler { + getConsentMeta() { + const consentData = this.getConsentData(); + if (consentData && this.generatedTime) { + return { + generatedAt: this.generatedTime, + } + } + } +} diff --git a/src/prebid.js b/src/prebid.js index f30b4ea9bbe..9ad72409990 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -45,7 +45,7 @@ import {executeRenderer, isRendererRequired} from './Renderer.js'; import {createBid} from './bidfactory.js'; import {storageCallbacks} from './storageManager.js'; import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js'; -import {default as adapterManager, gdprDataHandler, getS2SBidderSet, uspDataHandler} from './adapterManager.js'; +import {default as adapterManager, gdprDataHandler, getS2SBidderSet, gppDataHandler, uspDataHandler} from './adapterManager.js'; import CONSTANTS from './constants.json'; import * as events from './events.js'; import {newMetrics, useMetrics} from './utils/perfMetrics.js'; @@ -336,6 +336,7 @@ function getConsentMetadata() { return { gdpr: gdprDataHandler.getConsentMeta(), usp: uspDataHandler.getConsentMeta(), + gpp: gppDataHandler.getConsentMeta(), coppa: !!(config.getConfig('coppa')) } } @@ -628,6 +629,13 @@ $$PREBID_GLOBAL$$.requestBids = (function() { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + if (adUnitCodes && adUnitCodes.length) { + // if specific adUnitCodes supplied filter adUnits for those codes + adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); + } else { + // otherwise derive adUnitCodes from adUnits + adUnitCodes = adUnits && adUnits.map(unit => unit.code); + } const ortb2Fragments = { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) @@ -660,14 +668,6 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); adUnits = useMetrics(metrics).measureTime('requestBids.validate', () => checkAdUnitSetup(adUnits)); - if (adUnitCodes && adUnitCodes.length) { - // if specific adUnitCodes supplied filter adUnits for those codes - adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); - } else { - // otherwise derive adUnitCodes from adUnits - adUnitCodes = adUnits && adUnits.map(unit => unit.code); - } - function auctionDone(bids, timedOut, auctionId) { if (typeof bidsBackHandler === 'function') { try { @@ -915,6 +915,14 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { } }; +/** + * @alias module:pbjs.aliasRegistry + */ +$$PREBID_GLOBAL$$.aliasRegistry = adapterManager.aliasRegistry; +config.getConfig('aliasRegistry', config => { + if (config.aliasRegistry === 'private') delete $$PREBID_GLOBAL$$.aliasRegistry; +}); + /** * The bid response object returned by an external bidder adapter during the auction. * @typedef {Object} AdapterBidResponse diff --git a/src/utils/cpm.js b/src/utils/cpm.js new file mode 100644 index 00000000000..07113e7c944 --- /dev/null +++ b/src/utils/cpm.js @@ -0,0 +1,17 @@ +import {auctionManager} from '../auctionManager.js'; +import {bidderSettings} from '../bidderSettings.js'; +import {logError} from '../utils.js'; + +export function adjustCpm(cpm, bidResponse, bidRequest, {index = auctionManager.index, bs = bidderSettings} = {}) { + bidRequest = bidRequest || index.getBidRequest(bidResponse); + const bidCpmAdjustment = bs.get(bidResponse?.bidderCode || bidRequest?.bidder, 'bidCpmAdjustment'); + + if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { + try { + return bidCpmAdjustment(cpm, Object.assign({}, bidResponse), bidRequest); + } catch (e) { + logError('Error during bid adjustment', e); + } + } + return cpm; +} diff --git a/src/utils/promise.js b/src/utils/promise.js index 97c64a96f8b..69e40791ab1 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -90,6 +90,14 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { res.#parent = this; return res; } + + static resolve(value) { + return new this(resolve => resolve(value)) + } + + static reject(error) { + return new this((resolve, reject) => reject(error)) + } } /** diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 2680544d00b..3b3c05660df 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -218,6 +218,14 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withReferer = referer => { + Object.assign(ttxRequest.site, { + ref: referer + }); + + return this; + }; + this.withSchain = schain => { Object.assign(ttxRequest, { source: { @@ -1187,26 +1195,51 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when referer value is available', function() { - it('returns corresponding server requests with site.page set', function() { - const bidderRequest = { - refererInfo: { - page: 'http://foo.com/bar' - } - }; + context('when refererInfo values are available', function() { + context('when refererInfo.page is defined', function() { + it('returns corresponding server requests with site.page set', function() { + const bidderRequest = { + refererInfo: { + page: 'http://foo.com/bar' + } + }; - const ttxRequest = new TtxRequestBuilder() - .withBanner() - .withProduct() - .withPageUrl('http://foo.com/bar') - .build(); - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withPageUrl('http://foo.com/bar') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when refererInfo.ref is defined', function() { + it('returns corresponding server requests with site.ref set', function() { + const bidderRequest = { + refererInfo: { + ref: 'google.com' + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withReferer('google.com') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); }); }); @@ -1246,7 +1279,7 @@ describe('33acrossBidAdapter:', function () { }); context('when referer value is not available', function() { - it('returns corresponding server requests without site.page set', function() { + it('returns corresponding server requests without site.page and site.ref set', function() { const bidderRequest = { refererInfo: {} }; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 436d481c4a1..8abdc621922 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -149,10 +149,8 @@ describe('Adagio bid adapter', () => { site: { ext: { data: { - environment: 'desktop', pagetype: 'abc', - category: ['cat1', 'cat2', 'cat3'], - subcategory: [] + category: ['cat1', 'cat2', 'cat3'] } } } @@ -167,17 +165,11 @@ describe('Adagio bid adapter', () => { return utils.deepAccess(config, key); }); - setExtraParam(bid, 'environment'); - expect(bid.params.environment).to.equal('desktop'); - setExtraParam(bid, 'pagetype') expect(bid.params.pagetype).to.equal('article'); setExtraParam(bid, 'category'); expect(bid.params.category).to.equal('cat1'); // Only the first value is kept - - setExtraParam(bid, 'subcategory'); - expect(bid.params.subcategory).to.be.undefined; }); it('should use the adUnit param unit if defined', function() { @@ -784,8 +776,6 @@ describe('Adagio bid adapter', () => { adUnitElementId: 'gpt-adunit-code', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', supportIObs: true }, adUnitCode: 'adunit-code', @@ -833,8 +823,6 @@ describe('Adagio bid adapter', () => { site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { @@ -868,8 +856,6 @@ describe('Adagio bid adapter', () => { site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { @@ -1392,19 +1378,6 @@ describe('Adagio bid adapter', () => { }); describe.skip('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'); diff --git a/test/spec/modules/adlooxRtdProvider_spec.js b/test/spec/modules/adlooxRtdProvider_spec.js index 236e053e58c..5b99789981f 100644 --- a/test/spec/modules/adlooxRtdProvider_spec.js +++ b/test/spec/modules/adlooxRtdProvider_spec.js @@ -1,5 +1,6 @@ import adapterManager from 'src/adapterManager.js'; import analyticsAdapter from 'modules/adlooxAnalyticsAdapter.js'; +import {auctionManager} from 'src/auctionManager.js'; import { config as _config } from 'src/config.js'; import { expect } from 'chai'; import * as events from 'src/events.js'; @@ -75,14 +76,6 @@ describe('Adloox RTD Provider', function () { done(); }); - it('should reject non-string config.params.api_origin', function (done) { - const ret = rtdProvider.init({ params: { api_origin: null } }); - - expect(ret).is.false; - - done(); - }); - it('should reject less than one config.params.imps', function (done) { const ret = rtdProvider.init({ params: { imps: 0 } }); @@ -147,31 +140,37 @@ describe('Adloox RTD Provider', function () { }); let server = null; - let __config = null, CONFIG = null; - let getConfigStub, setConfigStub; + let CONFIG = null; beforeEach(function () { server = sinon.createFakeServer(); - __config = {}; CONFIG = utils.deepClone(config); - getConfigStub = sinon.stub(_config, 'getConfig').callsFake(function (path) { - return utils.deepAccess(__config, path); - }); - setConfigStub = sinon.stub(_config, 'setConfig').callsFake(function (obj) { - utils.mergeDeep(__config, obj); - }); }); afterEach(function () { - setConfigStub.restore(); - getConfigStub.restore(); - getConfigStub = setConfigStub = undefined; CONFIG = null; - __config = null; server.restore(); server = null; }); it('should fetch segments', function (done) { - const req = {}; + const req = { + adUnitCodes: [ adUnit.code ], + ortb2Fragments: { + global: { + site: { + ext: { + data: { + } + } + }, + user: { + ext: { + data: { + } + } + } + } + } + }; const adUnitWithSegments = utils.deepClone(adUnit); const getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ adUnits: [ adUnitWithSegments ] @@ -201,119 +200,28 @@ describe('Adloox RTD Provider', function () { }); it('should set ad server targeting', function (done) { - utils.deepSetValue(__config, 'ortb2.site.ext.data.adloox_rtd.ok', true); - const adUnitWithSegments = utils.deepClone(adUnit); utils.deepSetValue(adUnitWithSegments, 'ortb2Imp.ext.data.adloox_rtd.dis', [ 50, 60 ]); const getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ adUnits: [ adUnitWithSegments ] }); - const targetingData = rtdProvider.getTargetingData([ adUnitWithSegments.code ], CONFIG, null, { - getFPD: () => ({ - global: __config.ortb2 - }) + const auction = { adUnits: [ adUnitWithSegments ] }; + const getAuctionStub = sinon.stub(auctionManager.index, 'getAuction').returns({ + adUnits: [ adUnitWithSegments ], + getFPD: () => { return { global: { site: { ext: { data: { adloox_rtd: { ok: true } } } } } } } }); + + const targetingData = rtdProvider.getTargetingData([ adUnitWithSegments.code ], CONFIG, null, auction); expect(Object.keys(targetingData).length).is.equal(1); expect(Object.keys(targetingData[adUnit.code]).length).is.equal(2); expect(targetingData[adUnit.code].adl_ok).is.equal(1); expect(targetingData[adUnit.code].adl_dis.length).is.equal(2); + getAuctionStub.restore(); getGlobalStub.restore(); done(); }); }); - - describe('measure atf', function () { - const adUnitCopy = utils.deepClone(adUnit); - - const ratio = 0.38; - const [ [width, height] ] = utils.getAdUnitSizes(adUnitCopy); - - before(function () { - adapterManager.enableAnalytics({ - provider: analyticsAdapterName, - options: analyticsOptions - }); - expect(analyticsAdapter.context).is.not.null; - }); - - after(function () { - analyticsAdapter.disableAnalytics(); - expect(analyticsAdapter.context).is.null; - }); - - it(`should return ${ratio} for same-origin`, function (done) { - const el = document.createElement('div'); - el.setAttribute('id', adUnitCopy.code); - - const offset = height * ratio; - const elStub = sinon.stub(el, 'getBoundingClientRect').returns({ - top: 0 - (height - offset), - bottom: height - offset, - left: 0, - right: width - }); - - const querySelectorStub = sinon.stub(document, 'querySelector'); - querySelectorStub.withArgs(`#${adUnitCopy.code}`).returns(el); - - rtdProvider.atf(adUnitCopy, function(x) { - expect(x).is.equal(ratio); - - querySelectorStub.restore(); - elStub.restore(); - - done(); - }); - }); - - ('IntersectionObserver' in window ? it : it.skip)(`should return ${ratio} for cross-origin`, function (done) { - const frameElementStub = sinon.stub(window, 'frameElement').value(null); - - const el = document.createElement('div'); - el.setAttribute('id', adUnitCopy.code); - - const elStub = sinon.stub(el, 'getBoundingClientRect').returns({ - top: 0, - bottom: height, - left: 0, - right: width - }); - - const querySelectorStub = sinon.stub(document, 'querySelector'); - querySelectorStub.withArgs(`#${adUnitCopy.code}`).returns(el); - - let intersectionObserverStubFn = null; - const intersectionObserverStub = sinon.stub(window, 'IntersectionObserver').callsFake((fn) => { - intersectionObserverStubFn = fn; - return { - observe: (element) => { - expect(element).is.equal(el); - - intersectionObserverStubFn([{ - target: element, - intersectionRect: { width, height }, - intersectionRatio: ratio - }]); - }, - unobserve: (element) => { - expect(element).is.equal(el); - } - } - }); - - rtdProvider.atf(adUnitCopy, function(x) { - expect(x).is.equal(ratio); - - intersectionObserverStub.restore(); - querySelectorStub.restore(); - elStub.restore(); - frameElementStub.restore(); - - done(); - }); - }); - }); }); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 228b87ae4d5..26caaf5b70f 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,8 +4,10 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; +const BIDDER_CODE_ADX = 'admixeradx'; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; +const ENDPOINT_URL_ADX = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; describe('AdmixerAdapter', function () { @@ -16,18 +18,22 @@ describe('AdmixerAdapter', function () { expect(adapter.callBids).to.be.exist.and.to.be.a('function'); }); }); + // inv-nets.admixer.net/adxprebid.1.2.aspx describe('isBidRequestValid', function () { let bid = { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', }; it('should return true when required params found', function () { @@ -38,7 +44,7 @@ describe('AdmixerAdapter', function () { let bid = Object.assign({}, bid); delete bid.params; bid.params = { - 'placementId': 0 + placementId: 0, }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -47,22 +53,25 @@ describe('AdmixerAdapter', function () { describe('buildRequests', function () { let validRequest = [ { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, ]; let bidderRequest = { bidderCode: BIDDER_CODE, refererInfo: { - page: 'https://example.com' - } + page: 'https://example.com', + }, }; it('should add referrer and imp to be equal bidRequest', function () { @@ -81,49 +90,122 @@ describe('AdmixerAdapter', function () { it('sends bid request to CUSTOM_ENDPOINT via GET', function () { config.setBidderConfig({ bidders: [BIDDER_CODE], // one or more bidders - config: {[BIDDER_CODE]: {endpoint_url: ENDPOINT_URL_CUSTOM}} + config: { [BIDDER_CODE]: { endpoint_url: ENDPOINT_URL_CUSTOM } }, }); - const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(validRequest, bidderRequest) + ); expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); expect(request.method).to.equal('POST'); }); }); + describe('buildRequestsAdmixerADX', function () { + let validRequest = [ + { + bidder: BIDDER_CODE_ADX, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE_ADX, + refererInfo: { + page: 'https://example.com', + }, + }; + + it('sends bid request to ADX ENDPOINT', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT_URL_ADX); + expect(request.method).to.equal('POST'); + }); + }); + + describe('checkFloorGetting', function () { + let validRequest = [ + { + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE, + refererInfo: { + page: 'https://example.com', + }, + }; + it('gets floor', function () { + bidderRequest.getFloor = () => { + return { floor: 0.6 }; + }; + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = request.data; + expect(payload.bidFloor).to.deep.equal(0.6); + }); + }); + describe('interpretResponse', function () { let response = { body: { - ads: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
ad
', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'requestId': '5e4e763b6bc60b', - 'dealId': 'asd123', - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - }] - } + ads: [ + { + currency: 'USD', + cpm: 6.21, + ad: '
ad
', + width: 300, + height: 600, + creativeId: 'ccca3e5e-0c54-4761-9667-771322fbdffc', + ttl: 360, + netRevenue: false, + requestId: '5e4e763b6bc60b', + dealId: 'asd123', + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, + ], + }, }; it('should get correct bid response', function () { const ads = response.body.ads; let expectedResponse = [ { - 'requestId': ads[0].requestId, - 'cpm': ads[0].cpm, - 'creativeId': ads[0].creativeId, - 'width': ads[0].width, - 'height': ads[0].height, - 'ad': ads[0].ad, - 'currency': ads[0].currency, - 'netRevenue': ads[0].netRevenue, - 'ttl': ads[0].ttl, - 'dealId': ads[0].dealId, - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - } + requestId: ads[0].requestId, + cpm: ads[0].cpm, + creativeId: ads[0].creativeId, + width: ads[0].width, + height: ads[0].height, + ad: ads[0].ad, + currency: ads[0].currency, + netRevenue: ads[0].netRevenue, + ttl: ads[0].ttl, + dealId: ads[0].dealId, + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, ]; let result = spec.interpretResponse(response); @@ -141,18 +223,16 @@ describe('AdmixerAdapter', function () { describe('getUserSyncs', function () { let imgUrl = 'https://example.com/img1'; let frmUrl = 'https://example.com/frm2'; - let responses = [{ - body: { - cm: { - pixels: [ - imgUrl - ], - iframes: [ - frmUrl - ], - } - } - }]; + let responses = [ + { + body: { + cm: { + pixels: [imgUrl], + iframes: [frmUrl], + }, + }, + }, + ]; it('Returns valid values', function () { let userSyncAll = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, responses); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index ee585862800..b787a52d6f2 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -71,29 +71,29 @@ describe('adnuntiusBidAdapter', function () { } ] - const nativeBidderRequest = [ - { - bidId: '123', - bidder: 'adnuntius', - params: { - auId: '8b6bc', - network: 'adnuntius', - }, - mediaTypes: { - native: { - title: { - required: true - }, - image: { - required: true - }, - body: { - required: true - } - } - }, - } - ] + // const nativeBidderRequest = [ + // { + // bidId: '123', + // bidder: 'adnuntius', + // params: { + // auId: '8b6bc', + // network: 'adnuntius', + // }, + // mediaTypes: { + // native: { + // title: { + // required: true + // }, + // image: { + // required: true + // }, + // body: { + // required: true + // } + // } + // }, + // } + // ] const singleBidRequest = { bid: [ @@ -107,9 +107,9 @@ describe('adnuntiusBidAdapter', function () { bid: videoBidderRequest } - const nativeBidRequest = { - bid: nativeBidderRequest - } + // const nativeBidRequest = { + // bid: nativeBidderRequest + // } const serverResponse = { body: { @@ -237,83 +237,83 @@ describe('adnuntiusBidAdapter', function () { ] } } - const serverNativeResponse = { - body: { - 'adUnits': [ - { - 'auId': '000000000008b6bc', - 'targetId': '123', - 'html': '

hi!

', - 'matchedAdCount': 1, - 'responseId': 'adn-rsp-1460129238', - 'ads': [ - { - 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', - 'assets': { - 'image': { - 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', - 'width': '300', - 'height': '250' - } - }, - 'text': { - 'body': { - 'content': 'Testing Native ad from Adnuntius', - 'length': '32', - 'minLength': '0', - 'maxLength': '100' - }, - 'title': { - 'content': 'Native Ad', - 'length': '9', - 'minLength': '5', - 'maxLength': '100' - } - }, - 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'urls': { - 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' - }, - 'urlsEsc': { - 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' - }, - 'destinationUrls': { - 'destination': 'http://google.com' - }, - 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - 'cost': { 'amount': 0.005, 'currency': 'NOK' }, - 'impressionTrackingUrls': [], - 'impressionTrackingUrlsEsc': [], - 'adId': 'adn-id-1347343135', - 'selectedColumn': '0', - 'selectedColumnPosition': '0', - 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'creativeWidth': '980', - 'creativeHeight': '120', - 'creativeId': 'wgkq587vgtpchsx1', - 'lineItemId': 'scyjdyv3mzgdsnpf', - 'layoutId': 'sw6gtws2rdj1kwby', - 'layoutName': 'Responsive image' - }, - - ] - }, - { - 'auId': '000000000008b6bc', - 'targetId': '456', - 'matchedAdCount': 0, - 'responseId': 'adn-rsp-1460129238', - } - ] - } - } + // const serverNativeResponse = { + // body: { + // 'adUnits': [ + // { + // 'auId': '000000000008b6bc', + // 'targetId': '123', + // 'html': '

hi!

', + // 'matchedAdCount': 1, + // 'responseId': 'adn-rsp-1460129238', + // 'ads': [ + // { + // 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + // 'assets': { + // 'image': { + // 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', + // 'width': '300', + // 'height': '250' + // } + // }, + // 'text': { + // 'body': { + // 'content': 'Testing Native ad from Adnuntius', + // 'length': '32', + // 'minLength': '0', + // 'maxLength': '100' + // }, + // 'title': { + // 'content': 'Native Ad', + // 'length': '9', + // 'minLength': '5', + // 'maxLength': '100' + // } + // }, + // 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'urls': { + // 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + // }, + // 'urlsEsc': { + // 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + // }, + // 'destinationUrls': { + // 'destination': 'http://google.com' + // }, + // 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, + // 'bid': { 'amount': 0.005, 'currency': 'NOK' }, + // 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + // 'impressionTrackingUrls': [], + // 'impressionTrackingUrlsEsc': [], + // 'adId': 'adn-id-1347343135', + // 'selectedColumn': '0', + // 'selectedColumnPosition': '0', + // 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'creativeWidth': '980', + // 'creativeHeight': '120', + // 'creativeId': 'wgkq587vgtpchsx1', + // 'lineItemId': 'scyjdyv3mzgdsnpf', + // 'layoutId': 'sw6gtws2rdj1kwby', + // 'layoutName': 'Responsive image' + // }, + + // ] + // }, + // { + // 'auId': '000000000008b6bc', + // 'targetId': '456', + // 'matchedAdCount': 0, + // 'responseId': 'adn-rsp-1460129238', + // } + // ] + // } + // } describe('inherited functions', function () { it('exists and is a function', function () { @@ -531,21 +531,21 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); }); }); - describe('interpretNativeResponse', function () { - it('should return valid response when passed valid server response', function () { - const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); - const ad = serverNativeResponse.body.adUnits[0].ads[0] - expect(interpretedResponse).to.have.lengthOf(1); - expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); - expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); - expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); - expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); - expect(interpretedResponse[0].netRevenue).to.equal(false); - expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); - expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); - expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); - }); - }); + // describe('interpretNativeResponse', function () { + // it('should return valid response when passed valid server response', function () { + // const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); + // const ad = serverNativeResponse.body.adUnits[0].ads[0] + // expect(interpretedResponse).to.have.lengthOf(1); + // expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); + // expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + // expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + // expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + // expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + // expect(interpretedResponse[0].netRevenue).to.equal(false); + // expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + // expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + // expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); + // expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); + // }); + // }); }); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index e6a153d501a..24cede20352 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -722,6 +722,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -736,6 +737,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -758,6 +760,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Bids['bid_id_1'].TransactionID).to.be.equal('bid_id_1_transaction_id'); expect(payload.Bids['bid_id_3'].TransactionID).to.be.equal('bid_id_3_transaction_id'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); }); it('sends bid request to endpoint setted by parameters', function () { diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 472b226ab8a..f58e49eb364 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -13,7 +13,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { banner: { @@ -26,7 +26,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { video: { @@ -110,7 +110,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -126,7 +126,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -143,7 +143,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', video: { size: [480, 40] } @@ -167,8 +167,8 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -192,8 +192,8 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -289,14 +289,25 @@ const WIN_NOTICE = { 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', - 'hb_adomain': 'tenutabene.it' + 'hb_adomain': 'example.com' }, + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', 'currency': [ 'USD' ], 'mediaType': 'banner', + 'advertiserDomains': [ + 'abc.com' + ], 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } + ], 'width': 300, 'height': 250, 'status': 'rendered', @@ -365,6 +376,62 @@ describe('Aidem adapter', () => { deepSetValue(validVideoRequest.params, 'video.size', [640, 480]) expect(spec.isBidRequestValid(validVideoRequest)).to.be.true }); + + it('BANNER: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.true + }); + + it('BANNER: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.true + }); + + it('VIDEO: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); }); describe('buildRequests', () => { @@ -385,7 +452,7 @@ describe('Aidem adapter', () => { expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_BANNER_REQUESTS.length) expect(payload.imp[0]).to.be.a('object').that.has.all.keys( - 'banner', 'id', 'mediatype', 'imp_ext', 'tid' + 'banner', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' ) expect(payload.imp[0].banner).to.be.a('object').that.has.all.keys( 'format', 'topframe' @@ -401,7 +468,7 @@ describe('Aidem adapter', () => { expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_VIDEO_REQUESTS.length) expect(payload.imp[0]).to.be.a('object').that.has.all.keys( - 'video', 'id', 'mediatype', 'imp_ext', 'tid' + 'video', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' ) expect(payload.imp[0].video).to.be.a('object').that.has.all.keys( 'format', 'mimes', 'minDuration', 'maxDuration', 'protocols' @@ -421,6 +488,14 @@ describe('Aidem adapter', () => { 'value', 'currency' ) }); + + it('should hav wpar keys in environment object', function () { + const requests = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); + const payload = JSON.parse(requests.data) + expect(payload).to.have.property('environment') + expect(payload.environment).to.be.a('object').that.have.property('wpar') + expect(payload.environment.wpar).to.be.a('object').that.has.keys('innerWidth', 'innerHeight') + }); }) describe('interpretResponse', () => { diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 1ae9bb56df4..92e5d28f42a 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -108,7 +108,7 @@ describe('alkimiBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [REQUEST] - const bidderRequest = spec.buildRequests(bidRequests, { + let requestData = { auctionId: '123', refererInfo: { page: 'http://test.com/path.html' @@ -119,7 +119,8 @@ describe('alkimiBidAdapter', function () { gdprApplies: true }, uspConsent: 'uspConsent' - }) + } + const bidderRequest = spec.buildRequests(bidRequests, requestData) it('should return a properly formatted request with eids defined', function () { expect(bidderRequest.data.eids).to.deep.equal(REQUEST.userIdAsEids) @@ -138,7 +139,7 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') - expect(bidderRequest.data.schain).to.deep.contains({ver: '1.0', complete: 1, nodes: [{asi: 'alkimi-onboarding.com', sid: '00001', hp: 1}]}) + expect(bidderRequest.data.schain).to.deep.contains({ ver: '1.0', complete: 1, nodes: [{ asi: 'alkimi-onboarding.com', sid: '00001', hp: 1 }] }) expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner', adUnitCode: 'bannerAdUnitCode' }) expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') @@ -147,6 +148,17 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.options.contentType).to.equal('application/json') expect(bidderRequest.url).to.equal(ENDPOINT) }) + + it('sends bidFloor when configured', () => { + const requestWithFloor = Object.assign({}, REQUEST); + requestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && arg.mediaType === 'banner' && JSON.stringify(arg.size) === JSON.stringify([300, 250])) { + return { currency: 'USD', floor: 0.3 } + } + } + const bidderRequestFloor = spec.buildRequests([requestWithFloor], requestData); + expect(bidderRequestFloor.data.signRequest.bids[0].bidFloor).to.be.equal(0.3); + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 57ce37145f4..471c76f55cf 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -488,6 +488,20 @@ describe('AolAdapter', function () { expect(request.url).to.contain(NEXAGE_URL + 'dcn=2c9d2b50015c5ce9db6aeeed8b9500d6&pos=header'); }); + it('should return One Mobile url with configured GPP data', function () { + let bidRequest = createCustomBidRequest({ + params: getNexageGetBidParams() + }); + bidRequest.ortb2 = { + regs: { + gpp: 'testgpp', + gpp_sid: [8] + } + } + let [request] = spec.buildRequests(bidRequest.bids, bidRequest); + expect(request.url).to.contain('gpp=testgpp&gpp_sid=8'); + }); + it('should return One Mobile url with cmd=bid option', function () { let bidRequest = createCustomBidRequest({ params: getNexageGetBidParams() @@ -794,6 +808,14 @@ describe('AolAdapter', function () { 'euconsent=test-consent;gdpr=1;us_privacy=test-usp-consent;'); }); + it('should return formatted gpp privacy params when formatConsentData returns GPP data', function () { + formatConsentDataStub.returns({ + gpp: 'gppstring', + gpp_sid: [6, 7] + }); + expect(spec.formatMarketplaceDynamicParams()).to.be.equal('gpp=gppstring;gpp_sid=6%2C7;'); + }); + it('should return formatted params when formatKeyValues returns data', function () { formatKeyValuesStub.returns({ param1: 'val1', diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 5cd189da9a1..a5bd9a3592e 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -985,6 +985,52 @@ describe('AppNexusAdapter', function () { expect(payload.us_privacy).to.exist.and.to.equal(consentString); }); + it('should add gpp information to the request via bidderRequest.gppConsent', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': [8] + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.privacy).to.exist; + expect(payload.privacy.gpp).to.equal(consentString); + expect(payload.privacy.gpp_sid).to.deep.equal([8]); + }); + + it('should add gpp information to the request via bidderRequest.ortb2.regs', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'ortb2': { + 'regs': { + 'gpp': consentString, + 'gpp_sid': [7] + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.privacy).to.exist; + expect(payload.privacy.gpp).to.equal(consentString); + expect(payload.privacy.gpp_sid).to.deep.equal([7]); + }); + it('supports sending hybrid mobile app parameters', function () { let appRequest = Object.assign({}, bidRequests[0], @@ -1584,7 +1630,10 @@ describe('AppNexusAdapter', function () { 'phone': '1234567890', 'address': '28 W 23rd St, New York, NY 10010', 'privacy_link': 'https://appnexus.com/?url=privacy_url', - 'javascriptTrackers': '' + 'javascriptTrackers': '', + 'video': { + 'content': '' + } }; let bidderRequest = { bids: [{ @@ -1598,6 +1647,7 @@ describe('AppNexusAdapter', function () { expect(result[0].native.body).to.equal('Cool description great stuff'); expect(result[0].native.cta).to.equal('Do it'); expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + expect(result[0].native.video.content).to.equal(''); }); } @@ -1685,7 +1735,7 @@ describe('AppNexusAdapter', function () { it('should add advertiserDomains', function () { let responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + responseAdvertiserId.tags[0].ads[0].adomain = '123'; let bidderRequest = { bids: [{ @@ -1695,7 +1745,7 @@ describe('AppNexusAdapter', function () { } let result = spec.interpretResponse({ body: responseAdvertiserId }, { bidderRequest }); expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); - expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + expect(result[0].meta.advertiserDomains).to.deep.equal(['123']); }); }); diff --git a/test/spec/modules/appushBidAdapter_spec.js b/test/spec/modules/appushBidAdapter_spec.js new file mode 100644 index 00000000000..91c50cc3dd0 --- /dev/null +++ b/test/spec/modules/appushBidAdapter_spec.js @@ -0,0 +1,372 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/appushBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'appush' + +describe('AppushBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, 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://hb.appush.com/pbjs'); + }); + + it('Returns general data 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', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + 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.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, 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 = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, 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([], bidderRequest); + 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', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + 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', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + 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', 'meta'); + 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'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + 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', 'meta'); + 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'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + 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; + }); + }); +}); diff --git a/test/spec/modules/arcspanRtdProvider_spec.js b/test/spec/modules/arcspanRtdProvider_spec.js new file mode 100644 index 00000000000..c75075d8e05 --- /dev/null +++ b/test/spec/modules/arcspanRtdProvider_spec.js @@ -0,0 +1,187 @@ +import { arcspanSubmodule } from 'modules/arcspanRtdProvider.js'; +import { expect } from 'chai'; +import { loadExternalScript } from 'src/adloader.js'; + +describe('arcspanRtdProvider', function () { + describe('init', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('successfully initializes with a valid silo ID', function () { + expect(arcspanSubmodule.init(getGoodConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://silo13.p7cloud.net/as.js'); + loadExternalScript.resetHistory(); + }); + + it('fails to initialize with a missing silo ID', function () { + expect(arcspanSubmodule.init(getBadConfig())).to.equal(false); + expect(loadExternalScript.called).to.be.not.ok; + loadExternalScript.resetHistory(); + }); + + it('drops localhost script for test silo', function () { + expect(arcspanSubmodule.init(getTestConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://localhost:8080/as.js'); + loadExternalScript.resetHistory(); + }); + }); + + describe('alterBidRequests', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('alters the bid request 1', function () { + setIAB({ + raw: { + images: [ + 'Religion & Spirituality', + 'Medical Health>Substance Abuse', + 'Religion & Spirituality>Astrology', + 'Medical Health', + 'Events & Attractions', + ], + }, + codes: { + images: ['IAB23-10', 'IAB7', 'IAB7-42', 'IAB15-1'], + }, + newcodes: { + images: ['150', '453', '311', '456', '286'], + }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Religion & Spirituality,Medical Health>Substance Abuse,Religion & Spirituality>Astrology,Medical Health,Events & Attractions' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '150' }, + { id: '453' }, + { id: '311' }, + { id: '456' }, + { id: '286' } + ]); + }); + }); + + it('alters the bid request 2', function () { + setIAB({ + raw: { text: ['Sports', 'Sports>Soccer'] }, + codes: { text: ['IAB17', 'IAB17-44'] }, + newcodes: { text: ['483', '533'] }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Sports,Sports>Soccer' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB17', + 'IAB17_44' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '483' }, + { id: '533' } + ]); + }); + }); + }); +}); + +function getGoodConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 13, + }, + }; +} + +function getBadConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + notasilo: 1, + }, + }; +} + +function getTestConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 'test', + }, + }; +} + +function setIAB(vjson) { + window.arcobj2 = {}; + window.arcobj2.cat = 0; + if (typeof vjson.codes != 'undefined') { + window.arcobj2.cat = 1; + if (typeof vjson.codes.images != 'undefined') { + vjson.codes.images.forEach(function f(e, i) { + vjson.codes.images[i] = e.replace('-', '_'); + }); + } + if (typeof vjson.codes.text != 'undefined') { + vjson.codes.text.forEach(function f(e, i) { + vjson.codes.text[i] = e.replace('-', '_'); + }); + } + window.arcobj2.sampled = 1; + window.arcobj1 = {}; + window.arcobj1.page_iab_codes = vjson.codes; + window.arcobj1.page_iab = vjson.raw; + window.arcobj1.page_iab_newcodes = vjson.newcodes; + } +} diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index b8994b86847..8e96bd76940 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -130,7 +130,7 @@ const getConfigCreative = () => { creativeId: '34567erty', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -145,7 +145,7 @@ const getConfigCreativeVideo = (isNoVast) => { requestId: '6a204ce130280d', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -395,7 +395,7 @@ const testsInterpretResponse = [ mediaType: 'video', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(vastXml.replace(/\\"/g, '"')) }] @@ -421,7 +421,7 @@ const testsInterpretResponse = [ mediaType: 'banner', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, width: 300 }] }, @@ -505,7 +505,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -535,7 +535,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -552,7 +552,7 @@ const testsBuildBid = [ height: 250, creativeId: getConfigCreative().creativeId, ad: getConfigBannerBid().creative.banner.adm, - ttl: 3600, + ttl: 300, netRevenue: true, } } diff --git a/test/spec/modules/brightcomSSPBidAdapter_spec.js b/test/spec/modules/brightcomSSPBidAdapter_spec.js new file mode 100644 index 00000000000..6f35a7a290b --- /dev/null +++ b/test/spec/modules/brightcomSSPBidAdapter_spec.js @@ -0,0 +1,411 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/brightcomSSPBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; + +const URL = 'https://rt.marphezis.com/hb'; + +describe('brightcomSSPBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'bcmssp', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + page: 'http://example.com/page.html', + domain: 'example.com', + } + }; + 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); + }); + + it('sends us_privacy', function () { + const bidderRequest = { + uspConsent: '1YYY' + }; + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) + + expect(data.regs).to.not.equal(null); + expect(data.regs.ext).to.not.equal(null); + expect(data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('sends coppa', function () { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const data = JSON.parse(spec.buildRequests(bidRequests).data) + expect(data.regs).to.not.be.undefined; + expect(data.regs.coppa).to.equal(1); + }); + + it('sends schain', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data).to.not.be.undefined; + expect(data.source).to.not.be.undefined; + expect(data.source.ext).to.not.be.undefined; + expect(data.source.ext.schain).to.not.be.undefined; + expect(data.source.ext.schain.complete).to.equal(1); + expect(data.source.ext.schain.ver).to.equal('1.0'); + expect(data.source.ext.schain.nodes).to.not.be.undefined; + expect(data.source.ext.schain.nodes).to.lengthOf(1); + expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(data.source.ext.schain.nodes[0].hp).to.equal(1); + expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + + it('sends user eid parameters', function () { + bidRequests[0].userIdAsEids = [{ + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.eids).to.not.be.undefined; + expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + }); + + it('sends user id parameters', function () { + const userId = { + sharedid: { + id: '01*******', + third: '01E*******' + } + }; + + bidRequests[0].userId = userId; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.ids).is.deep.equal(userId); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'] + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 71e94f0da32..adb9137d892 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -56,6 +56,93 @@ describe('ColossussspAdapter', function () { referer: 'http://www.example.com', reachedTop: true, }, + ortb2: { + app: { + name: 'myappname', + keywords: 'power tools, drills', + content: { + data: [ + { + name: 'www.dataprovider1.com', + ext: { + segtax: 6 + }, + segment: [ + { + id: '687' + }, + { + id: '123' + } + ] + }, + { + name: 'www.dataprovider1.com', + ext: { + segtax: 7 + }, + segment: [ + { + id: '456' + }, + { + id: '789' + } + ] + } + ] + } + }, + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'], + page: 'https://page.example.com/here.html', + ref: 'https://ref.example.com', + keywords: 'power tools, drills', + search: 'drill', + content: { + userrating: '4', + data: [{ + name: 'www.dataprovider1.com', + ext: { + segtax: 7, + cids: ['iris_c73g5jq96mwso4d8'] + }, + segment: [ + { id: '687' }, + { id: '123' } + ] + }] + }, + ext: { + data: { + pageType: 'article', + category: 'repair' + } + } + }, + user: { + yob: 1985, + gender: 'm', + keywords: 'a,b', + data: [{ + name: 'dataprovider.com', + ext: { segtax: 4 }, + segment: [ + { id: '1' } + ] + }], + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + }, bids: [bid] } @@ -91,7 +178,7 @@ describe('ColossussspAdapter', function () { 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', 'ccpa', 'gdpr_consent', 'gdpr_require'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require', 'userObj', 'siteObj', 'appObj'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); @@ -132,7 +219,7 @@ describe('ColossussspAdapter', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require', 'userObj', 'siteObj', 'appObj'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); diff --git a/test/spec/modules/confiantRtdProvider_spec.js b/test/spec/modules/confiantRtdProvider_spec.js new file mode 100644 index 00000000000..987aca2e020 --- /dev/null +++ b/test/spec/modules/confiantRtdProvider_spec.js @@ -0,0 +1,107 @@ +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js' +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; + +import confiantModule from '../../../modules/confiantRtdProvider.js'; + +const { + injectConfigScript, + setupPage, + subscribeToConfiantCommFrame, + registerConfiantSubmodule +} = confiantModule; + +describe('Confiant RTD module', function () { + describe('setupPage()', function() { + it('should return false if propertId is not present in config', function() { + expect(setupPage({})).to.be.false; + expect(setupPage({ params: {} })).to.be.false; + expect(setupPage({ params: { propertyId: '' } })).to.be.false; + }); + + it('should return true if expected parameters are present', function() { + expect(setupPage({ params: { propertyId: 'clientId' } })).to.be.true; + }); + }); + + describe('Module initialization', function() { + let insertElementStub; + beforeEach(function() { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function() { + utils.insertElement.restore(); + }); + + it('should subscribe to rovided Window object', function () { + const mockWindow = { addEventListener: sinon.spy() }; + + subscribeToConfiantCommFrame(mockWindow); + + sinon.assert.calledOnce(mockWindow.addEventListener); + }); + + it('should fire BillableEvent as a result for message in comm window', function() { + let listenerCallback; + const mockWindow = { addEventListener: (a, cb) => (listenerCallback = cb) }; + let billableEventsCounter = 0; + + events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { + if (e.vendor === 'confiant') { + billableEventsCounter++; + expect(e.type).to.equal('impression'); + expect(e.billingId).to.be.a('number'); + expect(e.auctionId).to.equal('auctionId'); + expect(e.transactionId).to.equal('transactionId'); + } + }); + + subscribeToConfiantCommFrame(mockWindow); + listenerCallback({ + data: { + auctionId: 'auctionId', + transactionId: 'transactionId' + } + }); + listenerCallback({ + data: { + auctionId: 'auctionId', + transactionId: 'transactionId' + } + }); + + expect(billableEventsCounter).to.equal(2); + }); + }); + + describe('Sumbodule execution', function() { + let submoduleStub; + let insertElementStub; + beforeEach(function () { + submoduleStub = sinon.stub(hook, 'submodule'); + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function () { + utils.insertElement.restore(); + submoduleStub.restore(); + }); + + function initModule() { + registerConfiantSubmodule(); + + expect(submoduleStub.calledOnceWith('realTimeData')).to.equal(true); + + const registeredSubmoduleDefinition = submoduleStub.getCall(0).args[1]; + expect(registeredSubmoduleDefinition).to.be.an('object'); + expect(registeredSubmoduleDefinition).to.have.own.property('name', 'confiant'); + expect(registeredSubmoduleDefinition).to.have.own.property('init').that.is.a('function'); + + return registeredSubmoduleDefinition; + } + + it('should register Confiant submodule', function () { + initModule(); + }); + }); +}); diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index b2193f350ce..52639c39baf 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -31,7 +31,11 @@ describe('Yahoo ConnectID Submodule', () => { gdprApplies: 1, consentString: 'GDPR_CONSENT_STRING' }, - uspConsent: 'USP_CONSENT_STRING' + uspConsent: 'USP_CONSENT_STRING', + gppConsent: { + gppString: 'header~section6~section7', + applicableSections: [6, 7] + } }; }); @@ -157,7 +161,9 @@ describe('Yahoo ConnectID Submodule', () => { '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' }; const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -178,7 +184,9 @@ describe('Yahoo ConnectID Submodule', () => { '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' }; const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -201,7 +209,9 @@ describe('Yahoo ConnectID Submodule', () => { '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' }; const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -221,7 +231,9 @@ describe('Yahoo ConnectID Submodule', () => { '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' }; const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js new file mode 100644 index 00000000000..1170f418caf --- /dev/null +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -0,0 +1,577 @@ +import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData } from 'modules/consentManagementGpp.js'; +import { gppDataHandler } from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import 'src/prebid.js'; + +let expect = require('chai').expect; + +describe('consentManagementGpp', function () { + describe('setConsentConfig tests:', function () { + describe('empty setConsentConfig value', function () { + beforeEach(function () { + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + it('should use system default values', function () { + setConsentConfig({ + gpp: {} + }); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + sinon.assert.callCount(utils.logInfo, 3); + }); + + it('should exit consent manager if config is not an object', function () { + setConsentConfig(''); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); + + it('should exit consentManagement module if config is "undefined"', function () { + setConsentConfig(undefined); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); + + it('should not produce any consent metadata', function () { + setConsentConfig(undefined) + let consentMetadata = gppDataHandler.getConsentMeta(); + expect(consentMetadata).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }) + + it('should immediately look up consent data', () => { + setConsentConfig({ + gpp: { + cmpApi: 'invalid' + } + }); + expect(gppDataHandler.ready).to.be.true; + }) + }); + + describe('valid setConsentConfig value', function () { + afterEach(function () { + config.resetConfig(); + }); + + it('results in all user settings overriding system defaults', function () { + let allConfig = { + gpp: { + cmpApi: 'iab', + timeout: 7500 + } + }; + + setConsentConfig(allConfig); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(7500); + }); + + it('should recognize config.gpp, with default cmpApi and timeout', function () { + setConsentConfig({ + gpp: {} + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + }); + + it('should enable gppDataHandler', () => { + setConsentConfig({ + gpp: {} + }); + expect(gppDataHandler.enabled).to.be.true; + }); + }); + + describe('static consent string setConsentConfig value', () => { + afterEach(() => { + config.resetConfig(); + }); + + it('results in user settings overriding system defaults for v2 spec', () => { + let staticConfig = { + gpp: { + cmpApi: 'static', + timeout: 7500, + consentData: { + applicableSections: [7], + gppString: 'ABCDEFG1234', + gppVersion: 1, + sectionId: 3, + sectionList: [] + } + } + }; + + setConsentConfig(staticConfig); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + const consent = gppDataHandler.getConsentData(); + expect(consent.gppString).to.eql(staticConfig.gpp.consentData.gppString); + expect(consent.fullGppData).to.eql(staticConfig.gpp.consentData); + expect(staticConsentData).to.be.equal(staticConfig.gpp.consentData); + }); + }); + }); + + describe('requestBidsHook tests:', function () { + let goodConfig = { + gpp: { + cmpApi: 'iab', + timeout: 7500, + } + }; + + const staticConfig = { + gpp: { + cmpApi: 'static', + timeout: 7500, + consentData: { + gppString: 'abc12345', + applicableSections: [7] + } + } + } + + let didHookReturn; + + beforeEach(resetConsentData); + after(resetConsentData); + + describe('error checks:', function () { + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logWarn'); + sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + utils.logWarn.restore(); + utils.logError.restore(); + config.resetConfig(); + }); + + it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', function () { + let badCMPConfig = { + gpp: { + cmpApi: 'bad' + } + }; + setConsentConfig(badCMPConfig); + expect(userCMP).to.be.equal(badCMPConfig.gpp.cmpApi); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logWarn); + expect(didHookReturn).to.be.true; + expect(consent).to.be.null; + }); + + it('should call gppDataHandler.setConsentData() when unknown CMP api is used', () => { + setConsentConfig({ + gpp: { + cmpApi: 'invalid' + } + }); + let hookRan = false; + requestBidsHook(() => { + hookRan = true; + }, {}); + expect(hookRan).to.be.true; + expect(gppDataHandler.ready).to.be.true; + }) + + it('should throw proper errors when CMP is not found', function () { + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + // throw 2 errors; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config) + sinon.assert.calledTwice(utils.logError); + expect(didHookReturn).to.be.false; + expect(consent).to.be.null; + expect(gppDataHandler.ready).to.be.true; + }); + + it('should not trip when adUnits have no size', () => { + setConsentConfig(staticConfig); + let ran = false; + requestBidsHook(() => { + ran = true; + }, { + adUnits: [{ + code: 'test', + mediaTypes: { + video: {} + } + }] + }); + return gppDataHandler.promise.then(() => { + expect(ran).to.be.true; + }); + }); + + it('should continue the auction immediately, without consent data, if timeout is 0', (done) => { + setConsentConfig({ + gpp: { + cmpApi: 'iab', + timeout: 0 + } + }); + window.__gpp = function () {}; + try { + requestBidsHook(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([]); + expect(consent.gppString).to.be.undefined; + done(); + }, {}) + } finally { + delete window.__gpp; + } + }); + }); + + describe('already known consentData:', function () { + let cmpStub = sinon.stub(); + + function mockCMP(cmpResponse) { + return function (...args) { + if (args[0] === 'addEventListener') { + args[1](({ + eventName: 'sectionChange' + })); + } else if (args[0] === 'getGPPData') { + return cmpResponse; + } + } + } + + beforeEach(function () { + didHookReturn = false; + window.__gpp = function () {}; + }); + + afterEach(function () { + config.resetConfig(); + cmpStub.restore(); + delete window.__gpp; + resetConsentData(); + }); + + it('should bypass CMP and simply use previously stored consentData', function () { + let testConsentData = { + applicableSections: [7], + gppString: 'xyz', + }; + + cmpStub = sinon.stub(window, '__gpp').callsFake(mockCMP(testConsentData)); + setConsentConfig(goodConfig); + requestBidsHook(() => {}, {}); + cmpStub.reset(); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + + expect(didHookReturn).to.be.true; + expect(consent.gppString).to.equal(testConsentData.gppString); + expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); + sinon.assert.notCalled(cmpStub); + }); + }); + + describe('iframe tests', function () { + let cmpPostMessageCb = () => {}; + let stringifyResponse; + + function createIFrameMarker(frameName) { + let ifr = document.createElement('iframe'); + ifr.width = 0; + ifr.height = 0; + ifr.name = frameName; + document.body.appendChild(ifr); + return ifr; + } + + function creatCmpMessageHandler(prefix, returnEvtValue, returnGPPValue) { + return function (event) { + if (event && event.data) { + let data = event.data; + if (data[`${prefix}Call`]) { + let callId = data[`${prefix}Call`].callId; + let response; + if (data[`${prefix}Call`].command === 'addEventListener') { + response = { + [`${prefix}Return`]: { + callId, + returnValue: returnEvtValue, + success: true + } + } + } else if (data[`${prefix}Call`].command === 'getGPPData') { + response = { + [`${prefix}Return`]: { + callId, + returnValue: returnGPPValue, + success: true + } + } + } + event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + } + } + } + } + + function testIFramedPage(testName, messageFormatString, tarConsentString, tarSections) { + it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { + stringifyResponse = messageFormatString; + setConsentConfig(goodConfig); + requestBidsHook(() => { + let consent = gppDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(consent.gppString).to.equal(tarConsentString); + expect(consent.applicableSections).to.deep.equal(tarSections); + done(); + }, {}); + }); + } + + beforeEach(function () { + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logError.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + describe('v2 CMP workflow for iframe pages:', function () { + stringifyResponse = false; + let ifr2 = null; + + beforeEach(function () { + ifr2 = createIFrameMarker('__gppLocator'); + cmpPostMessageCb = creatCmpMessageHandler('__gpp', { + eventName: 'sectionChange' + }, { + gppString: 'abc12345234', + applicableSections: [7] + }); + window.addEventListener('message', cmpPostMessageCb, false); + }); + + afterEach(function () { + delete window.__gpp; // deletes the local copy made by the postMessage CMP call function + document.body.removeChild(ifr2); + window.removeEventListener('message', cmpPostMessageCb); + }); + + testIFramedPage('with/JSON response', false, 'abc12345234', [7]); + testIFramedPage('with/String response', true, 'abc12345234', [7]); + }); + }); + + describe('direct calls to CMP API tests', function () { + let cmpStub = sinon.stub(); + + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + config.resetConfig(); + cmpStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + resetConsentData(); + }); + + describe('v2 CMP workflow for normal pages:', function () { + beforeEach(function () { + window.__gpp = function () {}; + }); + + afterEach(function () { + delete window.__gpp; + }); + + it('performs lookup check and stores consentData for a valid existing user', function () { + let testConsentData = { + gppString: 'abc12345234', + applicableSections: [7] + }; + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.gppString).to.equal(testConsentData.gppString); + expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); + }); + + it('produces gdpr metadata', function () { + let testConsentData = { + gppString: 'abc12345234', + applicableSections: [7] + }; + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consentMeta = gppDataHandler.getConsentMeta(); + sinon.assert.notCalled(utils.logError); + expect(consentMeta.generatedAt).to.be.above(1644367751709); + }); + + it('throws an error when processCmpData check fails + does not call requestBids callback', function () { + let testConsentData = {}; + let bidsBackHandlerReturn = false; + + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + [utils.logWarn, utils.logError].forEach((stub) => stub.reset()); + + requestBidsHook(() => { + didHookReturn = true; + }, { + bidsBackHandler: () => bidsBackHandlerReturn = true + }); + let consent = gppDataHandler.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; + expect(gppDataHandler.ready).to.be.true; + }); + + describe('when proper consent is not available', () => { + let gppStub; + + function runAuction() { + setConsentConfig({ + gpp: { + cmpApi: 'iab', + timeout: 10, + } + }); + return new Promise((resolve, reject) => { + requestBidsHook(() => { + didHookReturn = true; + }, {}); + setTimeout(() => didHookReturn ? resolve() : reject(new Error('Auction did not run')), 20); + }) + } + + function mockGppCmp(gppdata) { + gppStub.callsFake((api, cb) => { + if (api === 'addEventListener') { + // eslint-disable-next-line standard/no-callback-literal + cb({ + pingData: { + cmpStatus: 'loaded' + } + }, true); + } + if (api === 'getGPPData') { + return gppdata; + } + }); + } + + beforeEach(() => { + gppStub = sinon.stub(window, '__gpp'); + }); + + afterEach(() => { + gppStub.restore(); + }) + + it('should continue auction with null consent when CMP is unresponsive', () => { + return runAuction().then(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([]); + expect(consent.gppString).to.be.undefined; + expect(gppDataHandler.ready).to.be.true; + }); + }); + + it('should use consent provided by events other than sectionChange', () => { + mockGppCmp({ + gppString: 'mock-consent-string', + applicableSections: [7] + }); + return runAuction().then(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([7]); + expect(consent.gppString).to.equal('mock-consent-string'); + expect(gppDataHandler.ready).to.be.true; + }); + }); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index f9c3cd5890e..e98486754ab 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/consentManagementUsp.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; -import adapterManager, {uspDataHandler} from 'src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler} from 'src/adapterManager.js'; import 'src/prebid.js'; import {defer} from '../../../src/utils/promise.js'; @@ -508,7 +508,20 @@ describe('consentManagement', function () { sinon.assert.notCalled(adapterManager.callDataDeletionRequest); listener(); sinon.assert.calledOnce(adapterManager.callDataDeletionRequest); - }) + }); + + it('does not fail if CMP does not support registerDeletion', () => { + sandbox.stub(window, '__uspapi').callsFake((cmd, _, cb) => { + if (cmd === 'registerDeletion') { + throw new Error('CMP not compliant'); + } else if (cmd === 'getUSPData') { + // eslint-disable-next-line standard/no-callback-literal + cb({uspString: 'string'}, true); + } + }); + setConsentConfig(goodConfig); + expect(uspDataHandler.getConsentData()).to.eql('string'); + }); }); }); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 47a2e2ab3d9..506b88ad839 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,15 @@ -import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData, gdprScope } from 'modules/consentManagement.js'; +import { + setConsentConfig, + requestBidsHook, + resetConsentData, + userCMP, + consentTimeout, + actionTimeout, + staticConsentData, + gdprScope, + loadConsentData, + setActionTimeout +} from 'modules/consentManagement.js'; import { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -726,4 +737,41 @@ describe('consentManagement', function () { }); }); }); + + describe('actionTimeout', function () { + afterEach(function () { + config.resetConfig(); + resetConsentData(); + }); + + it('should set actionTimeout if present', () => { + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(5000); + expect(actionTimeout).to.be.equal(5500); + }); + + it('should utilize actionTimeout duration on initial user visit when user action is pending', () => { + const cb = () => {}; + const cmpCallMap = { + 'iab': () => {}, + 'static': () => {} + }; + const timeout = sinon.spy(); + + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + loadConsentData(cb, cmpCallMap, timeout); + + sinon.assert.calledWith(timeout, sinon.match.any, 5000); + + setActionTimeout(); + + timeout.lastCall.lastArg === 5500; + }); + }); }); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d1b310624a6..556dce447b9 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -625,6 +625,52 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + it('should return a sync url if iframe syncs are enabled and GDPR applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and GDPR is undefined', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: undefined, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=0&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and USP applies', function () { + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?us_privacy=USP_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled, GDPR and USP applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING&us_privacy=USP_CONSENT_STRING'); + }) + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { let syncOptions = {'pixelEnabled': true}; let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE]); diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index f116b184b8c..88c54212aff 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -1,382 +1,296 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { config } from '../../../src/config.js'; -import { - spec, - CW_PAGE_VIEW_ID, - ENDPOINT_URL, - RENDERER_URL, -} from '../../../modules/cwireBidAdapter.js'; -import * as prebidGlobal from 'src/prebidGlobal.js'; - -// ------------------------------------ -// Bid Request Builder -// ------------------------------------ - -const BID_DEFAULTS = { - request: { - bidder: 'cwire', - auctionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - transactionId: 'txaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - bidId: 'bid123445', - bidderRequestId: 'brid12345', - code: 'original-div', - }, - params: { - placementId: 123456, - pageId: 777, - }, - sizes: [[300, 250], [1, 1]], -}; - -const BidderRequestBuilder = function BidderRequestBuilder(options) { - const defaults = { - bidderCode: 'cwire', - auctionId: BID_DEFAULTS.request.auctionId, - bidderRequestId: BID_DEFAULTS.request.bidderRequestId, - transactionId: BID_DEFAULTS.request.transactionId, - timeout: 3000, - }; - - const request = { - ...defaults, - ...options - }; - - this.build = () => request; -}; - -const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { - const defaults = JSON.parse(JSON.stringify(BID_DEFAULTS)); - - const request = { - ...defaults.request, - ...options - }; - - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request[k]; - }) - } - - this.withParams = (options, deleteKeys) => { - request.params = { - ...defaults.params, - ...options - }; - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request.params[k]; - }) - } - return this; - }; - - this.build = () => request; -}; +import {expect} from 'chai'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import {BID_ENDPOINT, spec, storage} from '../../../modules/cwireBidAdapter'; +import {deepClone, logInfo} from '../../../src/utils'; +import * as utils from 'src/utils.js'; +import {sandbox, stub} from 'sinon'; +import {config} from '../../../src/config'; describe('C-WIRE bid adapter', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); + config.setConfig({debug: true}); + const adapter = newBidder(spec); + let bidRequests = [ + { + 'bidder': 'cwire', + 'params': { + 'pageId': '4057', + 'placementId': 'ad-slot-bla' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + const response = { + body: { + 'cwid': '2ef90743-7936-4a82-8acf-e73382a64e94', + 'hash': '17112D98BBF55D3A', + 'bids': [{ + 'html': '

Hello world

', + 'cpm': 100, + 'currency': 'CHF', + 'dimensions': [1, 1], + 'netRevenue': true, + 'creativeId': '3454', + 'requestId': '2c634d4ca5ccfb', + 'placementId': 177, + 'transactionId': 'b4b32618-1350-4828-b6f0-fbb5c329e9a4', + 'ttl': 360 + }] + } + } + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + expect(spec.isBidRequestValid).to.exist.and.to.be.a('function'); + expect(spec.buildRequests).to.exist.and.to.be.a('function'); + expect(spec.interpretResponse).to.exist.and.to.be.a('function'); + }); }); - - afterEach(() => { - sandbox.restore(); - config.resetConfig(); + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(BID_ENDPOINT); + expect(request.method).to.equal('POST'); + }); }); + describe('buildRequests with given creative', function () { + let utilsStub; - // START TESTING - describe('C-WIRE - isBidRequestValid', function () { - it('should return true when required params found', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - expect(spec.isBidRequestValid(bid01)).to.equal(true); + before(function () { + utilsStub = stub(utils, 'getParameterByName').callsFake(function () { + return 'str-str' + }); }); - it('should fail if there is no placementId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + after(function () { + utilsStub.restore(); }); - it('should fail if invalid placementId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId; - bid01.placementId = '322'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + it('should add creativeId if url parameter given', function () { + // set from bid.params + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.cwcreative).to.exist; + expect(payload.cwcreative).to.deep.equal('str-str'); + }); + }) + + describe('buildRequests reads adUnit offsetWidth and offsetHeight', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + offsetWidth: 200, + offsetHeight: 250 + }); }); + it('width and height should be set', function () { + let bidRequest = deepClone(bidRequests[0]); - it('should fail if there is no pageId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) + + expect(el).to.exist; + expect(payload.slots[0].cwExt.dimensions.width).to.equal(200); + expect(payload.slots[0].cwExt.dimensions.height).to.equal(250); + expect(payload.slots[0].cwExt.style.maxHeight).to.not.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.not.exist; + }); + after(function () { + sandbox.restore() + }); + }); + describe('buildRequests reads style attributes', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + style: { + maxWidth: '400px', + maxHeight: '350px', + } + }); }); + it('css maxWidth should be set', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) - it('should fail if invalid pageId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId; - bid01.params.pageId = '3320'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + expect(el).to.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.eq('400px'); + !expect(payload.slots[0].cwExt.style.maxHeight).to.eq('350px'); }); + after(function () { + sandbox.restore() + }); + }); - it('should fail if cwcreative of type number', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.cwcreative; - bid01.params.cwcreative = 3320; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + describe('buildRequests reads feature flags', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'feature1,feature2' + }); }); - it('should pass with valid cwcreative of type string', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - bid01.params.cwcreative = 'i-am-a-string'; - expect(spec.isBidRequestValid(bid01)).to.equal(true); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.featureFlags).to.exist; + expect(payload.featureFlags).to.include.members(['feature1', 'feature2']); + }); + after(function () { + sandbox.restore() }); }); - describe('C-WIRE - buildRequests()', function () { - it('creates a valid request', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz-uuid', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('54321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz-uuid'); - expect(requests.data.refgroups[0]).to.equal('group_1'); + describe('buildRequests reads cwgroups flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'group1,group2' + }); }); - it('creates a valid request - read debug params from second bid', function () { - const bid01 = new BidRequestBuilder().withParams().build(); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); - const bid02 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('1234'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.refgroups).to.exist; + expect(payload.refgroups).to.include.members(['group1', 'group2']); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - read debug params from first bid, ignore second', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - cwapikey: 'api_key_33', - refgroups: 'group_33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_33'); - expect(requests.data.refgroups[0]).to.equal('group_33'); + describe('buildRequests reads debug flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'true' + }); }); - it('creates a valid request - read debug params from 3 different slots', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwapikey: 'api_key_5', - }).build(); - - const bid03 = new BidRequestBuilder() - .withParams({ - refgroups: 'group_5', - }).build(); - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest01); - - expect(requests.data.slots.length).to.equal(3); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.debug).to.exist; + expect(payload.debug).to.equal(true); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - config is overriden by URL params', function () { - // for whatever reason stub for getWindowLocation does not work - // so this was the closest way to test for get params - const params = sandbox.stub(utils, 'getParameterByName'); - params.withArgs('cwgroups').returns('group_2'); - params.withArgs('cwcreative').returns('654321'); - - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('654321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz'); - expect(requests.data.refgroups[0]).to.equal('group_2'); + describe('buildRequests reads cw_id from Localstorage', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => 'taerfagerg'); }); - it('creates a valid request - if params are not set, null or empty array are sent to the API', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + it('cw_id is set', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const requests = spec.buildRequests([bid01], bidderRequest01); + logInfo(JSON.stringify(payload)) - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal(null); - expect(requests.data.cwapikey).to.equal(null); - expect(requests.data.refgroups.length).to.equal(0); + expect(payload.cwid).to.exist; + expect(payload.cwid).to.equal('taerfagerg'); }); - }); + after(function () { + sandbox.restore() + }); + }) - describe('C-WIRE - interpretResponse()', function () { - const serverResponse = { - body: { - bids: [{ - html: '

AD CONTENT

', - currency: 'CHF', - cpm: 43.37, - dimensions: [1, 1], - netRevenue: true, - creativeId: '1337', - requestId: BID_DEFAULTS.request.bidId, - ttl: 3500, - }], - } - }; - - const expectedResponse = [{ - ad: JSON.parse(JSON.stringify(serverResponse.body.bids[0].html)), - bidderCode: BID_DEFAULTS.request.bidder, - cpm: JSON.parse(JSON.stringify(serverResponse.body.bids[0].cpm)), - creativeId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].creativeId)), - currency: JSON.parse(JSON.stringify(serverResponse.body.bids[0].currency)), - height: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[0])), - width: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[1])), - netRevenue: JSON.parse(JSON.stringify(serverResponse.body.bids[0].netRevenue)), - requestId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].requestId)), - ttl: JSON.parse(JSON.stringify(serverResponse.body.bids[0].ttl)), - meta: { - advertiserDomains: [], - }, - mediaType: 'banner', - }] - - it('correctly parses response', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + describe('buildRequests maps flattens params for legacy compat', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({}); + }); + it('pageId flattened', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); - const requests = spec.buildRequests([bid01], bidderRequest01); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const response = spec.interpretResponse(serverResponse, requests); - expect(response).to.deep.equal(expectedResponse); + logInfo(JSON.stringify(payload)) + + expect(payload.slots[0].pageId).to.exist; + }); + after(function () { + sandbox.restore() }); + }) - it('attaches renderer', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'outstream', - } - } - }).withParams().build(); - const bidderRequest01 = new BidderRequestBuilder().build(); + describe('pageId and placementId are required params', function () { + it('invalid request', function () { + let bidRequest = deepClone(bidRequests[0]); + delete bidRequest.params + + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.false; + }) - const _serverResponse = utils.deepClone(serverResponse); - _serverResponse.body.bids[0].vastXml = ''; + it('valid request', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const _expectedResponse = utils.deepClone(expectedResponse); - _expectedResponse[0].mediaType = 'video'; - _expectedResponse[0].videoScript = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].html)); - _expectedResponse[0].vastXml = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].vastXml)); - delete _expectedResponse[0].ad; + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - const requests = spec.buildRequests([bid01], bidderRequest01); - expect(requests.data.slots[0].sizes).to.deep.equal(['640x480']); + it('cwcreative must be of type string', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const response = spec.interpretResponse(_serverResponse, requests); - expect(response[0].renderer).to.exist; - expect(response[0].renderer.url).to.equals(RENDERER_URL); - expect(response[0].renderer.loaded).to.equals(false); + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - delete response[0].renderer; - expect(response).to.deep.equal(_expectedResponse); - }); + it('build request adds pageId', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.slots[0].pageId).to.exist; + }) + }); + + describe('process serverResponse', function () { + it('html to ad mapping', function () { + let bidResponse = deepClone(response); + const bids = spec.interpretResponse(bidResponse, {}); + + expect(bids[0].ad).to.exist; + }) }); }); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index d99318b5ddc..d80d0f3e875 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -707,7 +707,7 @@ describe('emx_digital Adapter', function () { })); }); - it('returns valid advertiser domain', function () { + it('returns valid advertiser domains', function () { const bidResponse = utils.deepClone(serverResponse); let result = spec.interpretResponse({body: bidResponse}); expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); @@ -724,6 +724,7 @@ describe('emx_digital Adapter', function () { 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.equal('https://biddr.brealtime.com/check.html') }); it('should pass gdpr params', function () { @@ -734,6 +735,34 @@ describe('emx_digital Adapter', function () { expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); expect(syncs[0].url).to.contains('gdpr=0'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=0&gdpr_consent=test') + }); + + it('should pass us_privacy string', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, { + 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('usp=test'); + }); + + it('should pass us_privacy and gdpr strings', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, + { + gdprApplies: true, + consentString: 'test' + }, + { + 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=1'); + expect(syncs[0].url).to.contains('usp=test'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=1&gdpr_consent=test&usp=test') }); }); }); diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 6db0b88a9bf..8cbd6907890 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -4,6 +4,7 @@ import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import {server} from 'test/mocks/xhr.js'; const CODE = 'feedad'; +const EXPECTED_ADAPTER_VERSION = '1.0.5'; describe('FeedAdAdapter', function () { describe('Public API', function () { @@ -300,6 +301,20 @@ describe('FeedAdAdapter', function () { expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); }); + it('should include adapter and prebid version', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids[0].params.prebid_adapter_version).to.equal(EXPECTED_ADAPTER_VERSION); + expect(result.data.bids[0].params.prebid_sdk_version).to.equal('$prebid.version$'); + }); }); describe('interpretResponse', function () { @@ -358,14 +373,20 @@ describe('FeedAdAdapter', function () { const pixelSync2 = {type: 'image', url: 'the pixel url 2'}; const iFrameSync1 = {type: 'iframe', url: 'the iFrame url 1'}; const iFrameSync2 = {type: 'iframe', url: 'the iFrame url 2'}; + const mockServerResponse = (content) => { + if (!(content instanceof Array)) { + content = [content]; + } + return content.map(it => ({body: it})); + }; it('should pass through the syncs out of the extension fields of the server response', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) expect(result).to.deep.equal([ pixelSync1, @@ -376,7 +397,7 @@ describe('FeedAdAdapter', function () { }); it('should concat the syncs of all responses', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1], iframes: [iFrameSync2], @@ -391,7 +412,7 @@ describe('FeedAdAdapter', function () { ext: { pixels: [pixelSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); expect(result).to.deep.equal([ pixelSync1, @@ -402,7 +423,7 @@ describe('FeedAdAdapter', function () { }); it('should filter out duplicates', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync1], iframes: [iFrameSync2, iFrameSync2], @@ -411,7 +432,7 @@ describe('FeedAdAdapter', function () { ext: { iframes: [iFrameSync2, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); expect(result).to.deep.equal([ pixelSync1, @@ -420,12 +441,12 @@ describe('FeedAdAdapter', function () { }); it('should not include iFrame syncs if the option is disabled', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse); expect(result).to.deep.equal([ pixelSync1, @@ -434,12 +455,12 @@ describe('FeedAdAdapter', function () { }); it('should not include pixel syncs if the option is disabled', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, serverResponse); expect(result).to.deep.equal([ iFrameSync1, @@ -448,15 +469,40 @@ describe('FeedAdAdapter', function () { }); it('should not include any syncs if the sync options are disabled or missing', function () { - const serverResponse = [{ + const serverResponse = mockServerResponse([{ ext: { pixels: [pixelSync1, pixelSync2], iframes: [iFrameSync1, iFrameSync2], } - }]; + }]); const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, serverResponse); expect(result).to.deep.equal([]); }); + + it('should handle empty responses', function () { + const serverResponse = mockServerResponse([]); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) + expect(result).to.deep.equal([]); + }); + + it('should not throw if the server response is weird', function () { + const responses = [ + mockServerResponse(null), + mockServerResponse('null'), + mockServerResponse(1234), + mockServerResponse({}), + mockServerResponse([{}, 123]), + ]; + responses.forEach(it => { + expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, it)).not.to.throw; + }); + }); + + it('should return empty array if the body extension is null', function () { + const response = mockServerResponse({ext: null}); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, response); + expect(result).to.deep.equal([]); + }); }); describe('event tracking calls', function () { @@ -592,7 +638,8 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.3' + prebid_adapter_version: EXPECTED_ADAPTER_VERSION, + prebid_sdk_version: '$prebid.version$', }; subject(data); expect(server.requests.length).to.equal(1); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 7ef576fc7ec..d1e0b055239 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -128,7 +128,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('should return a properly formatted request with schain defined', function () { const request = spec.buildRequests(bidRequests); const payload = request[0].data; - expect(payload.schain).to.deep.equal(bidRequests[0].schain) + expect(payload.schain).to.deep.equal('{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"example.com\",\"sid\":\"0\",\"hp\":1,\"rid\":\"bidrequestid\",\"domain\":\"example.com\"}]}'); }); it('sends bid request to ENDPOINT via GET', () => { diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index ca9eadc7fd4..c998ef2cf14 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -24,7 +24,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid?partner_id=0&_it=prebid`); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); it('gets a cached hadronid', function() { @@ -33,10 +33,8 @@ describe('HadronIdSystem', function () { }; getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1'); - const callbackSpy = sinon.spy(); - const callback = hadronIdSubmodule.getId(config).callback; - callback(callbackSpy); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'tstCachedHadronId1'}); + const result = hadronIdSubmodule.getId(config); + expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } }); }); it('allows configurable id url', function() { @@ -51,7 +49,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq('https://hadronid.publync.com?partner_id=0&_it=prebid'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); }); diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js new file mode 100644 index 00000000000..e55befd213a --- /dev/null +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -0,0 +1,194 @@ +import { expect } from 'chai' +import { spec } from 'modules/holidBidAdapter.js' + +describe('holidBidAdapterTests', () => { + const bidRequestData = { + bidder: 'holid', + adUnitCode: 'test-div', + bidId: 'bid-id', + auctionId: 'test-id', + params: { adUnitID: '12345' }, + mediaTypes: { banner: {} }, + sizes: [[300, 250]], + ortb2: { + site: { + publisher: { + domain: 'https://foo.bar', + } + }, + regs: { + gdpr: 1, + }, + user: { + ext: { + consent: 'G4ll0p1ng_Un1c0rn5', + } + }, + device: { + h: 410, + w: 1860, + } + } + } + + describe('isBidRequestValid', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + + it('should return true', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false when required params are not passed', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + delete bid.params.adUnitID + + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + const request = spec.buildRequests([bid], bid) + const payload = JSON.parse(request[0].data) + + it('should include ext in imp', () => { + expect(payload.imp[0].ext).to.exist + expect(payload.imp[0].ext).to.deep.equal({ + prebid: { storedrequest: { id: '12345' } }, + }) + }) + + it('should include banner format in imp', () => { + expect(payload.imp[0].banner).to.exist + expect(payload.imp[0].banner).to.deep.equal({ + format: [{ w: 300, h: 250 }], + }) + }) + + it('should include ortb2 first party data', () => { + expect(payload.device.w).to.equal(1860) + expect(payload.device.h).to.equal(410) + expect(payload.user.ext.consent).to.equal('G4ll0p1ng_Un1c0rn5') + expect(payload.regs.gdpr).to.equal(1) + }) + }) + + describe('interpretResponse', () => { + const serverResponse = { + body: { + id: 'test-id', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: 'testbidid', + price: 0.4, + adm: 'test-ad', + adid: 789456, + crid: 1234, + w: 300, + h: 250, + }, + ], + }, + ], + }, + } + + const interpretedResponse = spec.interpretResponse( + serverResponse, + bidRequestData + ) + + it('should interpret response', () => { + expect(interpretedResponse[0].requestId).to.equal(bidRequestData.bidId) + expect(interpretedResponse[0].cpm).to.equal( + serverResponse.body.seatbid[0].bid[0].price + ) + expect(interpretedResponse[0].ad).to.equal( + serverResponse.body.seatbid[0].bid[0].adm + ) + expect(interpretedResponse[0].creativeId).to.equal( + serverResponse.body.seatbid[0].bid[0].crid + ) + expect(interpretedResponse[0].width).to.equal( + serverResponse.body.seatbid[0].bid[0].w + ) + expect(interpretedResponse[0].height).to.equal( + serverResponse.body.seatbid[0].bid[0].h + ) + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.cur) + }) + }) + + describe('getUserSyncs', () => { + it('should return user sync', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: { + responsetimemillis: { + 'test seat 1': 2, + 'test seat 2': 1, + }, + }, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [ + { + type: 'iframe', + url: 'https://null.holid.io/sync.html?bidders=%5B%22test%20seat%201%22%2C%22test%20seat%202%22%5D&gdpr=1&gdpr_consent=dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig&usp_consent=mkjvbiniwot4827obfoy8sdg8203gb&type=iframe', + }, + ] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) + + it('should return empty user syncs when responsetimemillis is not defined', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: {}, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) + }) +}) diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 0de300e4a32..2327376d9ac 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -658,7 +658,7 @@ describe('IndexexchangeAdapter', function () { } }; - const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { + const DEFAULT_VIDEO_BID_RESPONSE_WITH_XML_ADM = { cur: 'USD', id: '1aa2bb3cc4de', seatbid: [ @@ -675,7 +675,6 @@ describe('IndexexchangeAdapter', function () { mtype: 2, adm: ' Test In-Stream Video ', + 'advertiserDomains': ['kueezrtb.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + const REQUEST = { data: { width: 300, @@ -83,7 +134,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); return parsedUrl.search; } catch (e) { return ''; @@ -111,6 +162,11 @@ describe('KueezRtbBidAdapter', function () { it('exists and is a string', function () { expect(adapter.code).to.exist.and.to.be.a('string'); }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); }); describe('validate bid requests', function () { @@ -155,7 +211,53 @@ describe('KueezRtbBidAdapter', function () { sandbox.stub(Date, 'now').returns(1000); }); - it('should build request for each size', function () { + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + prebidVersion: version, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }); + }); + + it('should build banner request for each size', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -179,6 +281,7 @@ describe('KueezRtbBidAdapter', function () { prebidVersion: version, schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', @@ -193,7 +296,7 @@ describe('KueezRtbBidAdapter', function () { }); describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', @@ -202,7 +305,7 @@ describe('KueezRtbBidAdapter', function () { }); it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -210,7 +313,7 @@ describe('KueezRtbBidAdapter', function () { }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ 'url': 'https://sync.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -226,16 +329,16 @@ describe('KueezRtbBidAdapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({price: 1, ad: ''}); + const responses = adapter.interpretResponse({ price: 1, ad: '' }); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); expect(responses).to.be.empty; }); - it('should return an array of interpreted responses', function () { + it('should return an array of interpreted banner responses', function () { const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); expect(responses[0]).to.deep.equal({ @@ -254,6 +357,26 @@ describe('KueezRtbBidAdapter', function () { }); }); + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['kueezrtb.com'] + } + }); + }); + it('should take default TTL', function () { const serverResponse = utils.deepClone(SERVER_RESPONSE); delete serverResponse.body.results[0].exp; @@ -271,11 +394,11 @@ describe('KueezRtbBidAdapter', function () { const userId = (function () { switch (idSystemProvider) { case 'lipb': - return {lipbid: id}; + return { lipbid: id }; case 'parrableId': - return {eid: id}; + return { eid: id }; case 'id5id': - return {uid: id}; + return { uid: id }; default: return id; } @@ -294,18 +417,18 @@ describe('KueezRtbBidAdapter', 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'}); + 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'}); + 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'); @@ -365,7 +488,7 @@ describe('KueezRtbBidAdapter', function () { now }); setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + const { value, created } = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -381,8 +504,8 @@ describe('KueezRtbBidAdapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({event: 'send'}); - const {event} = tryParseJSON(data); + const data = JSON.stringify({ event: 'send' }); + const { event } = tryParseJSON(data); expect(event).to.be.equal('send'); }); diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 5dc055ac080..ea538db08e1 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -18,6 +18,7 @@ describe('LotameId', function() { let removeFromLocalStorageStub; let timeStampStub; let uspConsentDataStub; + let requestHost; const nowTimestamp = new Date().getTime(); @@ -33,6 +34,11 @@ describe('LotameId', function() { ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + requestHost = 'https://c.ltmsphrcl.net/id'; + } else { + requestHost = 'https://id.crwdcntrl.net/id'; + } }); afterEach(function () { @@ -69,7 +75,7 @@ describe('LotameId', function() { }); it('should call the remote server when getId is called', function () { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); expect(callBackSpy.calledOnce).to.be.true; }); @@ -439,7 +445,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -471,7 +477,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -503,7 +509,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -531,7 +537,7 @@ describe('LotameId', function() { it('should not include the gdpr consent string on the url', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true' + `${requestHost}?gdpr_applies=true` ); }); }); @@ -560,7 +566,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -589,7 +595,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -613,7 +619,7 @@ describe('LotameId', function() { }); it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); }); }); @@ -835,7 +841,7 @@ describe('LotameId', function() { it('should pass the usp consent string and client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&us_privacy=1NNN&c=1234' + `${requestHost}?gdpr_applies=false&us_privacy=1NNN&c=1234` ); }); @@ -923,7 +929,7 @@ describe('LotameId', function() { it('should pass client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&c=1234' + `${requestHost}?gdpr_applies=false&c=1234` ); }); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 69fd7794ced..3606b4b4550 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -306,7 +306,7 @@ const ANALYTICS_MESSAGE = { describe('magnite analytics adapter', function () { let sandbox; let clock; - let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub; + let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub, removeDataFromLocalStorageStub; let gptSlot0; let gptSlotRenderEnded0; beforeEach(function () { @@ -325,6 +325,7 @@ describe('magnite analytics adapter', function () { getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + removeDataFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage') sandbox = sinon.sandbox.create(); localStorageIsEnabledStub.returns(true); @@ -355,6 +356,7 @@ describe('magnite analytics adapter', function () { getDataFromLocalStorageStub.restore(); setDataInLocalStorageStub.restore(); localStorageIsEnabledStub.restore(); + removeDataFromLocalStorageStub.restore(); magniteAdapter.disableAnalytics(); }); @@ -1932,19 +1934,21 @@ describe('magnite analytics adapter', function () { }); }); - it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { - let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); - inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); - inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; - expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); - - // not non-UTF char's in query / path which break if noDecodeWholeURL not set - inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + describe('getHostNameFromReferer', () => { + it('correctly grabs hostname from an input URL', function () { + let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); + inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); + inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; + expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); + + // not non-UTF char's in query / path which break if noDecodeWholeURL not set + inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + }); }); describe(`handle currency conversions`, () => { @@ -1985,4 +1989,22 @@ describe('magnite analytics adapter', function () { expect(bidResponseObj.bidPriceUSD).to.equal(0); }); }); + + describe('onDataDeletionRequest', () => { + it('attempts to delete the magnite cookie when local storage is enabled', () => { + magniteAdapter.onDataDeletionRequest(); + + expect(removeDataFromLocalStorageStub.getCall(0).args[0]).to.equal('mgniSession'); + }); + + it('throws an error if it cannot access the cookie', (done) => { + localStorageIsEnabledStub.returns(false); + try { + magniteAdapter.onDataDeletionRequest(); + } catch (error) { + expect(error.message).to.equal('Unable to access local storage, no data deleted'); + done(); + } + }) + }); }); diff --git a/test/spec/modules/medianetRtdProvider_spec.js b/test/spec/modules/medianetRtdProvider_spec.js index 7d73ecd5d44..f9d4ef7c2cf 100644 --- a/test/spec/modules/medianetRtdProvider_spec.js +++ b/test/spec/modules/medianetRtdProvider_spec.js @@ -66,12 +66,12 @@ describe('medianet realtime module', function () { describe('getTargeting should work correctly', function () { it('should return empty if not loaded', function () { window.mnjs.loaded = false; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([]), {}); + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([], {}, {}, {}), {}); }); it('should return ad unit codes when ad units are present', function () { const adUnitCodes = ['code1', 'code2']; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes), { + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes, {}, {}, {}), { code1: {'mnadc': 'code1'}, code2: {'mnadc': 'code2'}, }); @@ -79,7 +79,7 @@ describe('medianet realtime module', function () { it('should call mnjs.getTargetingData if loaded', function () { window.mnjs.loaded = true; - medianetRTD.medianetRtdModule.getTargetingData([]); + medianetRTD.medianetRtdModule.getTargetingData([], {}, {}, {}); assert.equal(getTargetingDataSpy.called, true); }); }); diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 90f0d8ae15c..571bb5fc584 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -49,7 +49,7 @@ describe('nextMillenniumBidAdapterTests', function() { cur: 'USD', ext: { sync: { - image: ['urlA'], + image: ['urlA?gdpr={{.GDPR}}'], iframe: ['urlB'], } } @@ -132,7 +132,7 @@ describe('nextMillenniumBidAdapterTests', function() { let userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.equal('image'); - expect(userSync[0].url).to.equal('urlA'); + expect(userSync[0].url).to.equal('urlA?gdpr=1'); syncOptions.iframeEnabled = true; syncOptions.pixelEnabled = false; @@ -142,6 +142,18 @@ describe('nextMillenniumBidAdapterTests', function() { expect(userSync[0].url).to.equal('urlB'); }); + it('Test getUserSyncs function if GDPR is undefined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + } + + let userSync = spec.getUserSyncs(syncOptions, [serverResponse], undefined, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.equal('urlA?gdpr=0'); + }); + it('Request params check without GDPR Consent', function () { delete bidRequestData[0].gdprConsent const request = spec.buildRequests(bidRequestData, bidRequestData[0]); diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index a5da8b29fbc..7645ee59f63 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -372,7 +372,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); expect(output[0].currency).to.be.eql(response.body.cur); @@ -411,7 +410,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); @@ -454,7 +452,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); expect(output[0].currency).to.be.eql(response.body.cur); @@ -490,7 +487,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 71e897c7f9e..5bd65cf0fd5 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -266,6 +266,27 @@ describe('onetag', function () { expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; }); + it('Should send GPP consent data', function () { + let consentString = 'consentString'; + let applicableSections = [1, 2, 3]; + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + gppString: consentString, + applicableSections: applicableSections + } + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload).to.exist; + expect(payload.gppConsent).to.exist; + expect(payload.gppConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.gppConsent.applicableSections).to.have.same.members(applicableSections); + }); it('Should send us privacy string', function () { let consentString = 'us_foo'; let bidderRequest = { @@ -375,6 +396,28 @@ describe('onetag', function () { expect(syncs[0].url).to.include(sync_endpoint); expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); }); + it('Must pass gpp consent string when gppConsent object is available', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gpp_consent=foo([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is null', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: null + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is empty', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, {}); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); it('Should send us privacy string', function () { let usConsentString = 'us_foo'; const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, usConsentString); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index cc1c2d1e607..8fe220aa202 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1729,6 +1729,47 @@ describe('OpenxAdapter', function () { }); }); }); + describe('with user agent client hints', function () { + it('should add json query param sua with BidRequest.device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); + expect(request[0].data.sua).to.exist; + const payload = JSON.parse(request[0].data.sua); + expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.sua).to.not.exist; + }); + }); }); }) diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index be6427f607b..3bb1958c5fe 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -579,6 +579,47 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device?.sua).to.not.exist; + }); + }); }); context('when there is a consent management framework', function () { diff --git a/test/spec/modules/orbitsoftBidAdapter_spec.js b/test/spec/modules/orbitsoftBidAdapter_spec.js new file mode 100644 index 00000000000..8c3187e9324 --- /dev/null +++ b/test/spec/modules/orbitsoftBidAdapter_spec.js @@ -0,0 +1,255 @@ +import {expect} from 'chai'; +import {spec} from 'modules/orbitsoftBidAdapter.js'; + +const ENDPOINT_URL = 'https://orbitsoft.com/php/ads/hb.phps'; +const REFERRER_URL = 'http://referrer.url/?_='; + +describe('Orbitsoft adapter', function () { + describe('implementation', function () { + describe('for requests', function () { + it('should accept valid bid', function () { + let validBid = { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + }, + isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject invalid bid', function () { + let invalidBid = { + bidder: 'orbitsoft' + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + describe('for requests', function () { + it('should accept valid bid with styles', function () { + let validBid = { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL, + style: { + title: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + description: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + url: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + colors: { + background: 'ffffff', + border: 'E0E0E0', + link: '5B99FE' + } + } + }, + refererInfo: {referer: REFERRER_URL}, + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + + let buildRequest = spec.buildRequests([validBid])[0]; + let requestUrl = buildRequest.url; + let requestUrlParams = buildRequest.data; + expect(requestUrl).to.equal(ENDPOINT_URL); + expect(requestUrlParams).have.property('f1', 'Tahoma'); + expect(requestUrlParams).have.property('fs1', 'medium'); + expect(requestUrlParams).have.property('w1', 'normal'); + expect(requestUrlParams).have.property('s1', 'normal'); + expect(requestUrlParams).have.property('c3', '0053F9'); + expect(requestUrlParams).have.property('f2', 'Tahoma'); + expect(requestUrlParams).have.property('fs2', 'medium'); + expect(requestUrlParams).have.property('w2', 'normal'); + expect(requestUrlParams).have.property('s2', 'normal'); + expect(requestUrlParams).have.property('c4', '0053F9'); + expect(requestUrlParams).have.property('f3', 'Tahoma'); + expect(requestUrlParams).have.property('fs3', 'medium'); + expect(requestUrlParams).have.property('w3', 'normal'); + expect(requestUrlParams).have.property('s3', 'normal'); + expect(requestUrlParams).have.property('c5', '0053F9'); + expect(requestUrlParams).have.property('c2', 'ffffff'); + expect(requestUrlParams).have.property('c1', 'E0E0E0'); + expect(requestUrlParams).have.property('c6', '5B99FE'); + }); + + it('should accept valid bid with custom params', function () { + let validBid = { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL, + customParams: { + cacheBuster: 'bf4d7c1', + clickUrl: 'http://testclickurl.com' + } + }, + refererInfo: {referer: REFERRER_URL}, + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + + let buildRequest = spec.buildRequests([validBid])[0]; + let requestUrlCustomParams = buildRequest.data; + expect(requestUrlCustomParams).have.property('c.cacheBuster', 'bf4d7c1'); + expect(requestUrlCustomParams).have.property('c.clickUrl', 'http://testclickurl.com'); + }); + + it('should reject invalid bid without requestUrl', function () { + let invalidBid = { + bidder: 'orbitsoft', + params: { + placementId: '123' + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject invalid bid without placementId', function () { + let invalidBid = { + bidder: 'orbitsoft', + params: { + requestUrl: ENDPOINT_URL + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + describe('bid responses', function () { + it('should return complete bid response', function () { + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 0.5, + width: 240, + height: 240, + content_url: 'https://orbitsoft.com/php/ads/hb.html', + adomain: ['test.adomain.tld'] + } + }; + + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + expect(bids).to.be.lengthOf(1); + expect(bids[0].cpm).to.equal(serverResponse.body.cpm); + expect(bids[0].width).to.equal(serverResponse.body.width); + expect(bids[0].height).to.equal(serverResponse.body.height); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adUrl).to.have.length.above(1); + expect(bids[0].adUrl).to.have.string('https://orbitsoft.com/php/ads/hb.html'); + expect(Object.keys(bids[0].meta)).to.include.members(['advertiserDomains']); + expect(bids[0].meta.advertiserDomains).to.deep.equal(serverResponse.body.adomain); + }); + + it('should return empty bid response', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 0 + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 1.5, + width: 0, + height: 0 + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with error', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = {error: 'error'}, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on empty body', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = {}, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + }); + }); +}); diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index 5d7bebc1de1..f5ce00ed8df 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -1,7 +1,7 @@ -import {expect} from 'chai'; -import {spec} from 'modules/outbrainBidAdapter.js'; -import {config} from 'src/config.js'; -import {server} from 'test/mocks/xhr'; +import { expect } from 'chai'; +import { spec } from 'modules/outbrainBidAdapter.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr'; import { createEidsArray } from 'modules/userId/eids.js'; describe('Outbrain Adapter', function () { @@ -45,6 +45,26 @@ describe('Outbrain Adapter', function () { ] } + const videoBidRequestParams = { + mediaTypes: { + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + } + describe('isBidRequestValid', function () { before(() => { config.setConfig({ @@ -93,6 +113,34 @@ describe('Outbrain Adapter', function () { } expect(spec.isBidRequestValid(bid)).to.equal(true) }) + it('should succeed when bid contains video', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...videoBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail when bid contains insufficient video information', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) it('should fail if publisher id is not set', function () { const bid = { bidder: 'outbrain', @@ -298,6 +346,61 @@ describe('Outbrain Adapter', function () { expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) + it('should build video request', function () { + const bidRequest = { + ...commonBidRequest, + ...videoBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + video: { + w: 640, + h: 480, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + mimes: ['video/mp4'], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + it('should pass optional parameters in request', function () { const bidRequest = { ...commonBidRequest, @@ -390,7 +493,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - config.setConfig({coppa: true}) + config.setConfig({ coppa: true }) const res = spec.buildRequests([bidRequest], commonBidderRequest) const resData = JSON.parse(res.data) @@ -412,7 +515,7 @@ describe('Outbrain Adapter', function () { let res = spec.buildRequests([bidRequest], commonBidderRequest); const resData = JSON.parse(res.data) expect(resData.user.ext.eids).to.deep.equal([ - {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} + { source: 'liveramp.com', uids: [{ id: 'id-value', atype: 3 }] } ]); }); @@ -421,7 +524,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - bidRequest.getFloor = function() { + bidRequest.getFloor = function () { return { currency: 'USD', floor: 1.23, @@ -619,6 +722,67 @@ describe('Outbrain Adapter', function () { const res = spec.interpretResponse(serverResponse, request) expect(res).to.deep.equal(expectedRes) }); + + it('should interpret video response', function () { + const serverResponse = { + body: { + id: '123', + seatbid: [ + { + bid: [ + { + id: '111', + impid: '1', + price: 1.1, + adm: '\u003cVAST version="3.0"\u003e\u003cAd\u003e\u003cInLine\u003e\u003cAdSystem\u003ezemanta\u003c/AdSystem\u003e\u003cAdTitle\u003e1\u003c/AdTitle\u003e\u003cImpression\u003ehttp://win.com\u003c/Impression\u003e\u003cImpression\u003ehttp://example.com/imptracker\u003c/Impression\u003e\u003cCreatives\u003e\u003cCreative\u003e\u003cLinear\u003e\u003cDuration\u003e00:00:25\u003c/Duration\u003e\u003cTrackingEvents\u003e\u003cTracking event="start"\u003ehttp://example.com/start\u003c/Tracking\u003e\u003cTracking event="progress" offset="00:00:03"\u003ehttp://example.com/p3s\u003c/Tracking\u003e\u003c/TrackingEvents\u003e\u003cVideoClicks\u003e\u003cClickThrough\u003ehttp://link.com\u003c/ClickThrough\u003e\u003c/VideoClicks\u003e\u003cMediaFiles\u003e\u003cMediaFile delivery="progressive" type="video/mp4" bitrate="700" width="640" height="360"\u003ehttps://example.com/123_360p.mp4\u003c/MediaFile\u003e\u003c/MediaFiles\u003e\u003c/Linear\u003e\u003c/Creative\u003e\u003c/Creatives\u003e\u003c/InLine\u003e\u003c/Ad\u003e\u003c/VAST\u003e', + adid: '100', + cid: '5', + crid: '29998660', + cat: ['cat-1'], + adomain: [ + 'example.com' + ], + nurl: 'http://example.com/win/${AUCTION_PRICE}' + } + ], + seat: '100', + group: 1 + } + ], + bidid: '456', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...videoBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'video', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + vastXml: 'zemanta1http://win.comhttp://example.com/imptracker00:00:25http://example.com/starthttp://example.com/p3shttp://link.comhttps://example.com/123_360p.mp4', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); }) }) @@ -637,41 +801,41 @@ describe('Outbrain Adapter', function () { }) it('should return user sync if pixel enabled with outbrain config', function () { - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + const ret = spec.getUserSyncs({ pixelEnabled: true }) + expect(ret).to.deep.equal([{ type: 'image', url: usersyncUrl }]) }) it('should not return user sync if pixel disabled', function () { - const ret = spec.getUserSyncs({pixelEnabled: false}) + const ret = spec.getUserSyncs({ pixelEnabled: false }) expect(ret).to.be.an('array').that.is.empty }) it('should not return user sync if url is not set', function () { config.resetConfig() - const ret = spec.getUserSyncs({pixelEnabled: true}) + const ret = spec.getUserSyncs({ pixelEnabled: true }) expect(ret).to.be.an('array').that.is.empty }) - it('should pass GDPR consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + it('should pass GDPR consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` }]); }); - it('should pass US consent', function() { + it('should pass US consent', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` }]); }); - it('should pass GDPR and US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + it('should pass GDPR and US consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }]); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9da242381be..cffc75f6949 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-trailing-spaces */ import {expect} from 'chai'; import { PrebidServer as Adapter, @@ -1505,6 +1506,28 @@ describe('S2S Adapter', function () { }); }); + it('site should not be present when app is present', function () { + const _config = { + s2sConfig: CONFIG, + app: { bundle: 'com.test.app' }, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.site).to.not.exist; + expect(requestBid.app).to.exist.and.to.be.a('object'); + }); + it('adds appnexus aliases to request', function () { config.setConfig({ s2sConfig: CONFIG }); @@ -1703,6 +1726,121 @@ describe('S2S Adapter', function () { }]); }); + describe('filterSettings', function () { + const getRequestBid = userSync => { + let cookieSyncConfig = utils.deepClone(CONFIG); + const s2sBidRequest = utils.deepClone(REQUEST); + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; + s2sBidRequest.s2sConfig = cookieSyncConfig; + + config.setConfig({ userSync, s2sConfig: cookieSyncConfig }); + + let bidRequest = utils.deepClone(BID_REQUESTS); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + return JSON.parse(server.requests[0].requestBody); + } + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the all key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['appnexus', 'rubicon', 'pubmatic'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + }, + 'iframe': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the iframe key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + iframe: { + bidders: ['rubicon', 'pubmatic'], + filter: 'include' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': '*', + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['rubicon', 'pubmatic'], + 'filter': 'include' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the image and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + image: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the all and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + }); + it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index b7d771814d0..f232631d73d 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1495,6 +1495,161 @@ describe('the price floors module', function () { }); }); + it('should use inverseFloorAdjustment function before bidder cpm adjustment', function () { + let functionUsed; + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Rubicon Adjustment'; + bidCpm *= 0.5; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Rubicon Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Appnexus Adjustment'; + bidCpm *= 0.75; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Appnexus Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.75; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // start with banner as only mediaType + bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus', + }; + + // should be same as the adjusted calculated inverses above test (banner) + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // should use rubicon inverse + expect(functionUsed).to.equal('Rubicon Inverse'); + + // appnexus just using banner should be same + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + + // now since asking for 'video' only mediaType inverse function should include the .18 + bidRequest.mediaTypes = { video: { context: 'instream' } }; + expect(bidRequest.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 2.36 + }); + + expect(functionUsed).to.equal('Rubicon Inverse'); + + // now since asking for 'video' inverse function should include the .18 + appnexusBid.mediaTypes = { video: { context: 'instream' } }; + expect(appnexusBid.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 1.5734 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + }); + + it('should pass inverseFloorAdjustment the bidRequest object so it can be used', function () { + // Adjustment factors based on Bid Media Type + const mediaTypeFactors = { + banner: 0.5, + native: 0.7, + video: 0.9 + } + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidCpm * mediaTypeFactors[bidResponse.mediaType]; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + // For the inverse we add up each mediaType in the request and divide by number of Mt's to get the inverse number + let factor = Object.keys(bidRequest.mediaTypes).reduce((sum, mediaType) => sum += mediaTypeFactors[mediaType], 0); + factor = factor / Object.keys(bidRequest.mediaTypes).length; + return bidCpm / factor; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // banner only should be 2 + bidRequest.mediaTypes = { banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // native only should be 1.4286 + bidRequest.mediaTypes = { native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video only should be 1.1112 + bidRequest.mediaTypes = { video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.1112 + }); + + // video and banner should even out to 0.7 factor so 1.4286 + bidRequest.mediaTypes = { video: {}, banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video and native should even out to 0.8 factor so -- 1.25 + bidRequest.mediaTypes = { video: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.25 + }); + + // banner and native should even out to 0.6 factor so -- 1.6667 + bidRequest.mediaTypes = { banner: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.6667 + }); + + // all 3 banner video and native should even out to 0.7 factor so -- 1.4286 + bidRequest.mediaTypes = { banner: {}, native: {}, video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + }); + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { getGlobal().bidderSettings = { rubicon: { diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 825b3abf432..60dca9e6da0 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -2,7 +2,6 @@ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; import {deepClone} from 'src/utils.js'; -import { config } from 'src/config.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -225,6 +224,8 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].banner).to.not.equal(null); expect(ortbRequest.imp[1].banner.w).to.equal(728); expect(ortbRequest.imp[1].banner.h).to.equal(90); + // tmax + expect(ortbRequest.tmax).to.equal(500); }); it('Verify parse response', function () { @@ -918,4 +919,13 @@ describe('PulsePoint Adapter Tests', function () { } }); }); + + it('Verify bid request timeouts', function () { + const mkRequest = (bidderRequest) => spec.buildRequests(slotConfigs, bidderRequest).data; + // assert default is used when no bidderRequest.timeout value is available + expect(mkRequest(bidderRequest).tmax).to.equal(500) + + // assert bidderRequest value is used when available + expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) + }); }); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 37100073407..4fa4ff354ec 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -116,6 +116,13 @@ describe('riseAdapter', function () { expect(request.data.bids[0].placementId).to.equal(placementId); }); + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('is_wrapper'); + expect(request.data.params.is_wrapper).to.equal(false); + }); + it('sends bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.url).to.equal(ENDPOINT); diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 81af9546ff0..6086db01de4 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -38,7 +38,7 @@ describe('SeedingAlliance adapter', function () { }); it('should have default request structure', function () { - let keys = 'site,device,cur,imp,user,regs'.split(','); + let keys = 'site,cur,imp,regs'.split(','); let validBidRequests = [{ bidId: 'bidId', params: {} @@ -60,14 +60,17 @@ describe('SeedingAlliance adapter', function () { assert.equal(request.id, validBidRequests[0].auctionId); }); - it('Verify the device', function () { + it('Verify the site url', function () { + let siteUrl = 'https://www.yourdomain.tld/your-directory/'; let validBidRequests = [{ bidId: 'bidId', - params: {} + params: { + url: siteUrl + } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); - assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.site.page, siteUrl); }); it('Verify native asset ids', function () { @@ -109,7 +112,7 @@ describe('SeedingAlliance adapter', function () { }); describe('interpretResponse', function () { - const goodResponse = { + const goodNativeResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', @@ -136,51 +139,91 @@ describe('SeedingAlliance adapter', function () { ] } }; + + const goodBannerResponse = { + body: { + cur: 'EUR', + id: 'b4516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [ + { + seat: 'seedingAlliance', + bid: [{ + adm: '', + impid: 1, + price: 0.90, + h: 250, + w: 300 + }] + } + ] + } + }; + const badResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', seatbid: [] }}; - const bidRequest = { + const bidNativeRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bidRequests: [{bidId: 'bidId1', nativeParams: {title: {required: true, len: 800}}}] + }; + + const bidBannerRequest = { + data: {}, + bidRequests: [{bidId: 'bidId1', sizes: [300, 250]}] }; it('should return null if body is missing or empty', function () { - const result = spec.interpretResponse(badResponse, bidRequest); + const result = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); delete badResponse.body - const result1 = spec.interpretResponse(badResponse, bidRequest); + const result1 = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); }); it('should return the correct params', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; - - assert.deepEqual(result[0].currency, goodResponse.body.cur); - assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); - assert.deepEqual(result[0].cpm, bid.price); - assert.deepEqual(result[0].creativeId, bid.crid); - assert.deepEqual(result[0].mediaType, 'native'); - assert.deepEqual(result[0].bidderCode, 'seedingAlliance'); + const resultNative = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bidNative = goodNativeResponse.body.seatbid[0].bid[0]; + + assert.deepEqual(resultNative[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultNative[0].currency, goodNativeResponse.body.cur); + assert.deepEqual(resultNative[0].requestId, bidNativeRequest.bidRequests[0].bidId); + assert.deepEqual(resultNative[0].cpm, bidNative.price); + assert.deepEqual(resultNative[0].creativeId, bidNative.crid); + assert.deepEqual(resultNative[0].mediaType, 'native'); + + const resultBanner = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + + assert.deepEqual(resultBanner[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultBanner[0].mediaType, 'banner'); + assert.deepEqual(resultBanner[0].width, bidBannerRequest.bidRequests[0].sizes[0]); + assert.deepEqual(resultBanner[0].height, bidBannerRequest.bidRequests[0].sizes[1]); }); - it('should return the correct tracking links', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; + it('should return the correct native tracking links', function () { + const result = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bid = goodNativeResponse.body.seatbid[0].bid[0]; const regExpPrice = new RegExp('price=' + bid.price); result[0].native.clickTrackers.forEach(function (clickTracker) { - assert.ok(clickTracker.search(regExpPrice) > -1); + assert.ok(clickTracker.search(regExpPrice) > -1); }); result[0].native.impressionTrackers.forEach(function (impTracker) { - assert.ok(impTracker.search(regExpPrice) > -1); + assert.ok(impTracker.search(regExpPrice) > -1); }); }); + + it('should return the correct banner content', function () { + const result = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + const bid = goodBannerResponse.body.seatbid[0].bid[0]; + const regExpContent = new RegExp(''); + + assert.ok(result[0].ad.search(regExpContent) > -1); + }); }); }); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index d0efd5f1f75..3627296975b 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -33,30 +33,60 @@ function createInStreamSlotConfig(mediaType) { }); } +const createBannerSlotConfig = (placement, mediatypes) => { + return getSlotConfigs(mediatypes || { banner: {} }, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement, + }); +}; + describe('Seedtag Adapter', function () { describe('isBidRequestValid method', function () { describe('returns true', function () { describe('when banner slot config has all mandatory params', () => { - describe('and placement has the correct value', function () { - const createBannerSlotConfig = (placement) => { - return getSlotConfigs( - { banner: {} }, - { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement, - } + const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; + placements.forEach((placement) => { + it(placement + 'should be valid', function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement) ); - }; - const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; - placements.forEach((placement) => { - it('should be ' + placement, function () { + expect(isBidRequestValid).to.equal(true); + }); + + it( + placement + + ' should be valid when has display and video mediatypes, and video context is outstream', + function () { const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement) + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }) ); expect(isBidRequestValid).to.equal(true); - }); - }); + } + ); + + it( + placement + + " shouldn't be valid when has display and video mediatypes, and video context is instream", + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(false); + } + ); }); }); describe('when video slot has all mandatory params', function () { @@ -70,7 +100,18 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid(slotConfig); expect(isBidRequestValid).to.equal(true); }); - it('should return true, when video context is instream, but placement is not inStream', function () { + it('should return true, when video context is instream and mediatype is video and banner', function () { + const slotConfig = createInStreamSlotConfig({ + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + banner: {}, + }); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(true); + }); + it('should return false, when video context is instream, but placement is not inStream', function () { const slotConfig = getSlotConfigs( { video: { @@ -395,6 +436,52 @@ describe('Seedtag Adapter', function () { expect(payload.schain).to.not.exist; }); }); + + describe('GPP param', function () { + it('should be added to payload when bidderRequest has gppConsent param', function () { + const gppConsent = { + gppString: 'someGppString', + applicableSections: [7] + } + bidderRequest['gppConsent'] = gppConsent + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.exist; + expect(data.gppConsent.gppString).to.equal(gppConsent.gppString); + expect(data.gppConsent.applicableSections[0]).to.equal(gppConsent.applicableSections[0]); + }); + + it('should be undefined on payload when bidderRequest has not gppConsent param', function () { + bidderRequest.gppConsent = undefined + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.be.undefined; + }); + + it('should be added to payload when bidderRequest has ortb2 param', function () { + const ortb2 = { + regs: { + gpp: 'someGppString', + gpp_sid: [7] + } + } + bidderRequest['gppConsent'] = undefined + bidderRequest['ortb2'] = ortb2; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.exist; + expect(data.gppConsent.gppString).to.equal(ortb2.regs.gpp); + expect(data.gppConsent.applicableSections[0]).to.equal(ortb2.regs.gpp_sid[0]); + }); + + it('should be added to payload when bidderRequest has neither gppConsent nor ortb2', function () { + bidderRequest['ortb2'] = undefined; + bidderRequest['gppConsent'] = undefined; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.be.undefined; + }); + }); }); describe('interpret response method', function () { @@ -533,11 +620,11 @@ describe('Seedtag Adapter', function () { const timeoutUrl = getTimeoutUrl(timeoutData); expect(timeoutUrl).to.equal( 'https://s.seedtag.com/se/hb/timeout?publisherToken=' + - params.publisherId + - '&adUnitId=' + - params.adUnitId + - '&timeout=' + - timeout + params.publisherId + + '&adUnitId=' + + params.adUnitId + + '&timeout=' + + timeout ); }); @@ -549,11 +636,11 @@ describe('Seedtag Adapter', function () { expect( utils.triggerPixel.calledWith( 'https://s.seedtag.com/se/hb/timeout?publisherToken=' + - params.publisherId + - '&adUnitId=' + - params.adUnitId + - '&timeout=' + - timeout + params.publisherId + + '&adUnitId=' + + params.adUnitId + + '&timeout=' + + timeout ) ).to.equal(true); }); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index db61983c9c9..97250ad0ebc 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import { spec } from 'modules/smartadserverBidAdapter.js'; @@ -394,7 +395,6 @@ describe('Smart bid adapter tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with GDPR', function () { @@ -443,10 +443,33 @@ describe('Smart bid adapter tests', function () { }); }); + describe('GPP', function () { + it('should be added to payload when gppConsent available in bidder request', function () { + const options = { + gppConsent: { + gppString: 'some-gpp-string', + applicableSections: [3, 5] + } + }; + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, options); + const payload = JSON.parse(request[0].data); + + expect(payload).to.have.property('gpp').and.to.equal(options.gppConsent.gppString); + expect(payload).to.have.property('gpp_sid').and.to.be.an('array'); + expect(payload.gpp_sid).to.have.lengthOf(2).and.to.deep.equal(options.gppConsent.applicableSections); + }); + + it('should be undefined on payload when gppConsent unavailable in bidder request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, {}); + const payload = JSON.parse(request[0].data); + + expect(payload.gpp).to.be.undefined; + }); + }); + describe('ccpa/us privacy tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with us privacy', function () { @@ -475,7 +498,6 @@ describe('Smart bid adapter tests', function () { describe('Instream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const INSTREAM_DEFAULT_PARAMS = [{ @@ -746,7 +768,6 @@ describe('Smart bid adapter tests', function () { describe('Outstream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const OUTSTREAM_DEFAULT_PARAMS = [{ @@ -1055,6 +1076,17 @@ describe('Smart bid adapter tests', function () { }); describe('Floors module', function () { + const getFloor = (bid) => { + switch (bid.mediaType) { + case BANNER: + return { currency: 'USD', floor: 1.93 }; + case VIDEO: + return { currency: 'USD', floor: 2.72 }; + default: + return {}; + } + }; + it('should include floor from bid params', function() { const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS))[0].data); expect(bidRequest.bidfloor).to.deep.equal(DEFAULT_PARAMS[0].params.bidfloor); @@ -1094,12 +1126,91 @@ describe('Smart bid adapter tests', function () { const floor = spec.getBidFloor(bidRequest, null); expect(floor).to.deep.equal(0); }); + + it('should take floor from bidder params over ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73, bidfloor: 1.25 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.25); + }); + + it('should take floor from banner ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.93); + }); + + it('should take floor from video ad unit', function() { + const bidRequest = [{ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.72); + }); + + it('should take floor from multiple media type ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 600]] + }, + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const requests = spec.buildRequests(bidRequest); + expect(requests).to.have.lengthOf(2); + + const requestContents = requests.map(r => JSON.parse(r.data)); + const videoRequest = requestContents.filter(r => r.videoData)[0]; + expect(videoRequest).to.not.equal(null).and.to.not.be.undefined; + expect(videoRequest).to.have.property('bidfloor').and.to.equal(2.72); + + const bannerRequest = requestContents.filter(r => !r.videoData)[0]; + expect(bannerRequest).to.not.equal(null).and.to.not.be.undefined; + expect(bannerRequest).to.have.property('bidfloor').and.to.equal(1.93); + }); }); describe('Verify bid requests with multiple mediaTypes', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); var DEFAULT_PARAMS_MULTIPLE_MEDIA_TYPES = [{ diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 5bd08064c79..c3d0711632e 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -374,6 +374,16 @@ describe('The smartx adapter', function () { } }) }); + + it('should pass sitekey param', function () { + var request; + + bid.params.sitekey = 'foo' + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.site.content.ext.sitekey).to.equal('foo'); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index 78503d7a6f0..b41b0280235 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -19,20 +19,116 @@ describe('SmartyTechDSPAdapter: inherited functions', function () { describe('SmartyTechDSPAdapter: isBidRequestValid', function () { it('Invalid bid request. Should return false', function () { - const invalidBidFixture = { + const bidFixture = { params: { use_id: 13144375 } } - expect(spec.isBidRequestValid(invalidBidFixture)).to.be.false + + expect(spec.isBidRequestValid(bidFixture)).to.be.false }); it('Valid bid request. Should return true', function () { - const validBidFixture = { + const bidFixture = { params: { endpointId: 13144375 } } - expect(spec.isBidRequestValid(validBidFixture)).to.be.true + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check video block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check playerSize', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check context', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid video bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'instream' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check banner block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check banner sizes', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid banner bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true }); }); @@ -42,12 +138,29 @@ function mockRandomSizeArray(len) { }); } -function mockBidRequestListData(size) { +function mockBidRequestListData(mediaType, size) { return Array.apply(null, {length: size}).map((i, index) => { const id = Math.floor(Math.random() * 800) * (index + 1); + let mediaTypes; + + if (mediaType == 'video') { + mediaTypes = { + video: { + playerSize: mockRandomSizeArray(1), + context: 'instream' + }, + } + } else { + mediaTypes = { + banner: { + sizes: mockRandomSizeArray(index + 1) + } + } + } + return { adUnitCode: `adUnitCode-${id}`, - sizes: mockRandomSizeArray(index + 1), + mediaTypes: mediaTypes, bidId: `bidId-${id}`, params: { endpointId: id @@ -66,18 +179,27 @@ function mockRefererData() { function mockResponseData(requestData) { let data = {} - requestData.data.forEach((request, index) => { - const sizeArrayIndex = Math.floor(Math.random() * (request.sizes.length - 1)); const rndIndex = Math.floor(Math.random() * 800); + let width, height, mediaType; + if (request.video !== undefined) { + width = request.video.playerSize[0][0]; + height = request.video.playerSize[0][1]; + mediaType = 'video'; + } else { + width = request.banner.sizes[0][0]; + height = request.banner.sizes[0][1]; + mediaType = 'banner'; + } data[request.adUnitCode] = { ad: `ad-${rndIndex}`, - width: request.sizes[sizeArrayIndex][0], - height: request.sizes[sizeArrayIndex][1], + width: width, + height: height, creativeId: `creative-id-${index}`, cpm: Math.floor(Math.random() * 100), - currency: `UAH-${rndIndex}` + currency: `UAH-${rndIndex}`, + mediaType: mediaType }; }); return { @@ -89,7 +211,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { let mockBidRequest; let mockReferer; beforeEach(() => { - mockBidRequest = mockBidRequestListData(8); + mockBidRequest = mockBidRequestListData('banner', 8); mockReferer = mockRefererData(); }); it('has return data', () => { @@ -108,7 +230,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { const data = spec.buildRequests(mockBidRequest, mockReferer).data; data.forEach((request, index) => { expect(request.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode); - expect(request.sizes).to.be.equal(mockBidRequest[index].sizes); + expect(request.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner); expect(request.bidId).to.be.equal(mockBidRequest[index].bidId); expect(request.endpointId).to.be.equal(mockBidRequest[index].params.endpointId); expect(request.referer).to.be.equal(mockReferer.refererInfo.page); @@ -122,7 +244,7 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData(2); + const brData = mockBidRequestListData('banner', 2); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { @@ -157,6 +279,42 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + }); + }); +}); + +describe('SmartyTechDSPAdapter: interpretResponse video', () => { + let mockBidRequest; + let mockReferer; + let request; + let mockResponse; + beforeEach(() => { + const brData = mockBidRequestListData('video', 2); + mockReferer = mockRefererData(); + request = spec.buildRequests(brData, mockReferer); + mockBidRequest = { + data: brData + } + mockResponse = mockResponseData(request); + }); + + it('interpretResponse: convert to correct data', () => { + const keys = Object.keys(mockResponse.body); + const data = spec.interpretResponse(mockResponse, mockBidRequest); + + data.forEach((responseItem, index) => { + expect(responseItem.ad).to.be.equal(mockResponse.body[keys[index]].ad); + expect(responseItem.cpm).to.be.equal(mockResponse.body[keys[index]].cpm); + expect(responseItem.creativeId).to.be.equal(mockResponse.body[keys[index]].creativeId); + expect(responseItem.currency).to.be.equal(mockResponse.body[keys[index]].currency); + expect(responseItem.netRevenue).to.be.true; + expect(responseItem.ttl).to.be.equal(60); + expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); + expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); + expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + expect(responseItem.vastXml).to.be.equal(mockResponse.body[keys[index]].ad); }); }); }); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 7803cfff394..a9382f092e2 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -287,6 +287,7 @@ describe('SonobiBidAdapter', function () { }, mediaTypes: { video: { + sizes: [[300, 250], [300, 600]], context: 'outstream' } } @@ -331,7 +332,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d||f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 5bde75cd0b6..0c01244033e 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' import * as utils from '../../../src/utils' +import {server} from '../../mocks/xhr' describe('Taboola Adapter', function () { let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie, commonBidRequest; @@ -91,6 +92,31 @@ describe('Taboola Adapter', function () { }) }) + describe('onBidWon', function () { + it('onBidWon exist as a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + + it('should resolve price macro in nurl', function () { + const nurl = 'http://win.example.com/${AUCTION_PRICE}'; + const bid = { + requestId: 1, + cpm: 2, + originalCpm: 3.4, + creativeId: 1, + ttl: 60, + netRevenue: true, + mediaType: 'banner', + ad: '...', + width: 300, + height: 250, + nurl: nurl + } + spec.onBidWon(bid); + expect(server.requests[0].url).to.equals('http://win.example.com/3.4') + }); + }); + describe('buildRequests', function () { const defaultBidRequest = { ...createBidRequest(), @@ -122,7 +148,8 @@ describe('Taboola Adapter', function () { }, 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, - 'bidfloorcur': 'USD' + 'bidfloorcur': 'USD', + 'ext': {} }], 'site': { 'id': commonBidRequest.params.publisherId, @@ -137,11 +164,13 @@ describe('Taboola Adapter', function () { 'source': {'fd': 1}, 'bcat': [], 'badv': [], + 'wlang': [], 'user': { 'buyeruid': 0, 'ext': {}, }, - 'regs': {'coppa': 0, 'ext': {}} + 'regs': {'coppa': 0, 'ext': {}}, + 'ext': {} }; const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); @@ -184,7 +213,7 @@ describe('Taboola Adapter', function () { expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); }); - it('should pass bid floor even if they is a bid floor param', function () { + it('should pass bid floor even if it is a bid floor param', function () { const optionalParams = { bidfloor: 0.25, bidfloorcur: 'EUR' @@ -206,6 +235,38 @@ describe('Taboola Adapter', function () { expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); }); + it('should pass impression position', function () { + const optionalParams = { + position: 2 + }; + + const bidRequest = { + ...defaultBidRequest, + params: {...commonBidRequest.params, ...optionalParams} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].banner.pos).to.deep.equal(2); + }); + + it('should pass gpid if configured', function () { + const ortb2Imp = { + ext: { + gpid: '/homepage/#1' + } + } + const bidRequest = { + ...defaultBidRequest, + ortb2Imp: ortb2Imp, + params: {...commonBidRequest.params} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].ext.gpid).to.deep.equal('/homepage/#1'); + }); + it('should pass bidder timeout', function () { const bidderRequest = { ...commonBidderRequest, @@ -216,6 +277,40 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500); }); + describe('first party data', function () { + it('should parse first party data', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + wlang: ['de'], + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.bcat).to.deep.equal(bidderRequest.ortb2.bcat) + expect(resData.badv).to.deep.equal(bidderRequest.ortb2.badv) + expect(resData.wlang).to.deep.equal(bidderRequest.ortb2.wlang) + }); + + it('should pass pageType if exists in ortb2', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + ext: { + data: { + pageType: 'news' + } + } + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.ext.pageType).to.deep.equal(bidderRequest.ortb2.ext.data.pageType); + }); + }); + describe('handle privacy segments when building request', function () { it('should pass GDPR consent', function () { const bidderRequest = { @@ -234,6 +329,20 @@ describe('Taboola Adapter', function () { expect(resData.regs.ext.gdpr).to.equal(1) }); + it('should pass GPP consent if exist in ortb2', function () { + const ortb2 = { + regs: { + gpp: 'testGpp', + gpp_sid: [1, 2, 3] + } + } + + const res = spec.buildRequests([defaultBidRequest], {...commonBidderRequest, ortb2}) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.gpp).to.equal('testGpp') + expect(resData.regs.ext.gpp_sid).to.deep.equal([1, 2, 3]) + }); + it('should pass us privacy consent', function () { const bidderRequest = { refererInfo: { @@ -351,7 +460,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + } ], 'seat': '14204545260' @@ -437,7 +548,8 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' }, { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', @@ -453,7 +565,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + } ], 'seat': '14204545260' @@ -477,6 +591,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[0].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -492,6 +607,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[1].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -516,6 +632,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -542,6 +659,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -552,8 +670,36 @@ describe('Taboola Adapter', function () { }); }) - describe('userData', function () { - // todo: add UT for getUserSyncs + describe('getUserSyncs', function () { + const usersyncUrl = 'https://trc.taboola.com/sg/prebidJS/1/cm'; + + it('should not return user sync if pixelEnabled is false', function () { + const res = spec.getUserSyncs({pixelEnabled: false}); + expect(res).to.be.an('array').that.is.empty; + }); + + it('should return user sync if pixelEnabled is true', function () { + const res = spec.getUserSyncs({pixelEnabled: true}); + expect(res).to.deep.equal([{type: 'image', url: usersyncUrl}]); + }); + + it('should pass consent tokens values', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'GDPR_CONSENT'}, 'USP_CONSENT')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT', 'GPP_STRING')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT&gpp=GPP_STRING` + }]); + }); }) describe('internal functions', function () { diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index adbc982e509..8180183e6d7 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -58,7 +58,7 @@ describe('TargetVideo Bid Adapter', function() { 'uuid': '84ab500420319d', 'ads': [{ 'ad_type': 'video', - 'cpm': 0.500000, + 'cpm': 0.675000, 'notify_url': 'https://www.target-video.com/', 'rtb': { 'video': { @@ -87,7 +87,7 @@ describe('TargetVideo Bid Adapter', function() { const bid = bidResponse[0]; expect(bid).to.not.be.empty; - expect(bid.cpm).to.equal(0.675); + expect(bid.cpm).to.equal(0.5); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.ad).to.include('') diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 5cfa64184f9..0447cb4d5d6 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -1168,6 +1168,19 @@ describe('triplelift adapter', function () { } }) }); + it('should add gpp consent data to bid request object if gpp data exists', function() { + bidderRequest.ortb2 = { + regs: { + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + } + } + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs).to.deep.equal({ + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + }) + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 99e0681e547..5403d842e02 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -897,7 +897,7 @@ describe('User ID', function () { storage: {name: 'intentIqId', type: 'cookie'} }, { name: 'hadronId', - storage: {name: 'hadronId', type: 'cookie'} + storage: {name: 'hadronId', type: 'html5'} }, { name: 'zeotapIdPlus' }, { @@ -1872,8 +1872,8 @@ describe('User ID', function () { it('test hook from hadronId html5', function (done) { // simulate existing browser local storage values - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'random-ls-identifier'})); - localStorage.setItem('hadronId_exp', ''); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); init(config); setSubmoduleRegistry([hadronIdSubmodule]); @@ -1883,15 +1883,15 @@ describe('User ID', function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('random-ls-identifier'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'audigent.com', - uids: [{id: 'random-ls-identifier', atype: 1}] + uids: [{id: 'testHadronId1', atype: 1}] }); }); }); localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp', ''); + localStorage.removeItem('hadronId_exp'); done(); }, {adUnits}); }); @@ -2125,7 +2125,9 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + // hadronId only supports localStorage + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); @@ -2149,7 +2151,7 @@ describe('User ID', function () { ['netId', 'netId', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['hadronId', 'hadronId', 'cookie'], + ['hadronId', 'hadronId', 'html5'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], @@ -2192,7 +2194,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); @@ -2231,7 +2233,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); @@ -2284,7 +2287,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); @@ -2320,7 +2324,7 @@ describe('User ID', function () { }, { name: 'zeotapIdPlus' }, { - name: 'hadronId', storage: {name: 'hadronId', type: 'cookie'} + name: 'hadronId', storage: {name: 'hadronId', type: 'html5'} }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { @@ -2388,7 +2392,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userId.uid2).to.deep.equal({ id: 'Sample_AD_Token' }); @@ -2420,7 +2424,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 2ddb65469af..24d565805d3 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -13,11 +13,13 @@ import { getUniqueDealId, getNextDealId, getVidazooSessionId, + webSessionId } from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; -import { version } from 'package.json'; -import { useFakeTimers } from 'sinon'; -import { BANNER, VIDEO } from '../../../src/mediaTypes'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; const SUB_DOMAIN = 'openrtb'; @@ -38,6 +40,10 @@ const BID = { 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'mediaTypes': [BANNER], @@ -53,6 +59,10 @@ const VIDEO_BID = { 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -85,6 +95,8 @@ const BIDDER_REQUEST = { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', @@ -94,6 +106,10 @@ const BIDDER_REQUEST = { 'site': { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'] + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] } }, }; @@ -147,7 +163,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -226,6 +242,9 @@ describe('VidazooBidAdapter', function () { it('should build video request', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -243,6 +262,15 @@ describe('VidazooBidAdapter', function () { gdpr: 1, gdprConsent: 'consent_string', usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidderRequestId: '12a8ae9ada9c13', gpid: '', prebidVersion: version, ptrace: '1000', @@ -256,6 +284,7 @@ describe('VidazooBidAdapter', function () { uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, + webSessionId: webSessionId, mediaTypes: { video: { api: [2], @@ -278,6 +307,9 @@ describe('VidazooBidAdapter', function () { }); it('should build banner request for each size', function () { + config.setConfig({ + bidderTimeout: 3000 + }); const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -288,6 +320,15 @@ describe('VidazooBidAdapter', function () { gdprConsent: 'consent_string', gdpr: 1, usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', @@ -311,7 +352,8 @@ describe('VidazooBidAdapter', function () { isStorageAllowed: true, gpid: '1234567890', cat: ['IAB2'], - pagecat: ['IAB2-2'] + pagecat: ['IAB2-2'], + webSessionId: webSessionId } }); }); @@ -324,7 +366,7 @@ describe('VidazooBidAdapter', function () { describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', @@ -333,7 +375,7 @@ describe('VidazooBidAdapter', function () { }); it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -341,7 +383,7 @@ describe('VidazooBidAdapter', function () { }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ 'url': 'https://sync.cootlogix.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -357,12 +399,12 @@ describe('VidazooBidAdapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({ price: 1, ad: '' }); + const responses = adapter.interpretResponse({price: 1, ad: ''}); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); expect(responses).to.be.empty; }); @@ -422,11 +464,11 @@ describe('VidazooBidAdapter', function () { const userId = (function () { switch (idSystemProvider) { case 'lipb': - return { lipbid: id }; + return {lipbid: id}; case 'parrableId': - return { eid: id }; + return {eid: id}; case 'id5id': - return { uid: id }; + return {uid: id}; default: return id; } @@ -445,18 +487,18 @@ 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' }); + 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' }); + 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'); @@ -569,7 +611,7 @@ describe('VidazooBidAdapter', function () { now }); setStorageItem('myKey', 2020); - const { value, created } = getStorageItem('myKey'); + const {value, created} = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -585,8 +627,8 @@ describe('VidazooBidAdapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({ event: 'send' }); - const { event } = tryParseJSON(data); + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); expect(event).to.be.equal('send'); }); diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js index 4377e989851..e08353f5b8d 100644 --- a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js @@ -66,7 +66,28 @@ function getUtilsMock() { const sharedUtils = { videoEvents }; +function addDiv() { + const div = document.createElement('div'); + div.setAttribute('id', 'test'); + document.body.appendChild(div); +} + +function removeDiv() { + const div = document.getElementById('test'); + if (div) { + div.remove(); + } +} + describe('JWPlayerProvider', function () { + beforeEach(() => { + addDiv(); + }); + + afterEach(() => { + removeDiv(); + }); + describe('init', function () { let config; let adState; @@ -75,7 +96,7 @@ describe('JWPlayerProvider', function () { let utilsMock; beforeEach(() => { - config = {}; + config = { divId: 'test' }; adState = adStateFactory(); timeState = timeStateFactory(); callbackStorage = callbackStorageFactory(); @@ -104,7 +125,21 @@ describe('JWPlayerProvider', function () { expect(payload.errorCode).to.be.equal(-2); }); - it('should instantiate the player when uninstantied', function () { + it('should trigger failure when div is missing', function () { + removeDiv(); + let jwplayerMock = () => {}; + const provider = JWPlayerProvider(config, jwplayerMock, adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-3); + addDiv(); + addDiv(); + }); + + it('should instantiate the player when uninstantiated', function () { const player = getPlayerMock(); config.playerConfig = {}; const setupSpy = player.setup = sinon.spy(); @@ -113,7 +148,7 @@ describe('JWPlayerProvider', function () { expect(setupSpy.calledOnce).to.be.true; }); - it('should trigger setup complete when player is already instantied', function () { + it('should trigger setup complete when player is already instantiated', function () { const player = getPlayerMock(); player.getState = () => 'idle'; const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); @@ -151,7 +186,7 @@ describe('JWPlayerProvider', function () { const test_playback_method = PLAYBACK_METHODS.CLICK_TO_PLAY; const test_skip = 0; - const config = {}; + const config = { divId: 'test' }; const player = getPlayerMock(); const utils = getUtilsMock(); @@ -230,9 +265,9 @@ describe('JWPlayerProvider', function () { duration: test_duration, playbackMode: test_playback_mode }) - } + }; - const provider = JWPlayerProvider({}, makePlayerFactoryMock(player), adStateFactory(), timeState, {}, utils, sharedUtils); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeState, {}, utils, sharedUtils); provider.init(); let content = provider.getOrtbContent(); @@ -260,7 +295,7 @@ describe('JWPlayerProvider', function () { it('should call playAd', function () { const player = getPlayerMock(); const playAdSpy = player.playAd = sinon.spy(); - const provider = JWPlayerProvider({}, makePlayerFactoryMock(player), {}, {}, {}, {}, sharedUtils); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), {}, {}, {}, {}, sharedUtils); provider.init(); provider.setAdTagUrl('tag'); expect(playAdSpy.called).to.be.true; @@ -273,7 +308,7 @@ describe('JWPlayerProvider', function () { it('should register event listener on player', function () { const player = getPlayerMock(); const onSpy = player.on = sinon.spy(); - const provider = JWPlayerProvider({}, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), getUtilsMock(), sharedUtils); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), getUtilsMock(), sharedUtils); provider.init(); const callback = () => {}; provider.onEvent(PLAY, callback, {}); @@ -285,7 +320,7 @@ describe('JWPlayerProvider', function () { it('should remove event listener on player', function () { const player = getPlayerMock(); const offSpy = player.off = sinon.spy(); - const provider = JWPlayerProvider({}, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), utils, sharedUtils); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), utils, sharedUtils); provider.init(); const callback = () => {}; provider.onEvent(AD_IMPRESSION, callback, {}); @@ -301,7 +336,7 @@ describe('JWPlayerProvider', function () { const player = getPlayerMock(); const removeSpy = player.remove = sinon.spy(); player.remove = removeSpy; - const provider = JWPlayerProvider({}, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), getUtilsMock(), sharedUtils); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), getUtilsMock(), sharedUtils); provider.init(); provider.destroy(); provider.destroy(); diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index 32bc94b73fa..01583ac30dc 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -3,6 +3,7 @@ import { config } from 'src/config.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { spec } from 'modules/yahoosspBidAdapter.js'; import {createEidsArray} from '../../../modules/userId/eids'; +import {deepClone} from '../../../src/utils'; const DEFAULT_BID_ID = '84ab500420319d'; const DEFAULT_BID_DCN = '2093845709823475'; @@ -713,7 +714,7 @@ describe('YahooSSP Bid Adapter:', () => { }); }); - describe('GDPR & Consent:', () => { + describe('GDPR & Consent & GPP:', () => { it('should return request objects that do not send cookies if purpose 1 consent is not provided', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); bidderRequest.gdprConsent = { @@ -731,6 +732,20 @@ describe('YahooSSP Bid Adapter:', () => { const options = spec.buildRequests(validBidRequests, bidderRequest)[0].options; expect(options.withCredentials).to.be.false; }); + + it('adds the ortb2 gpp consent info to the request', function () { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const ortb2 = { + regs: { + gpp: 'somegppstring', + gpp_sid: [6, 7] + } + }; + let clonedBidderRequest = {...bidderRequest, ortb2}; + const data = spec.buildRequests(validBidRequests, clonedBidderRequest)[0].data; + expect(data.regs.ext.gpp).to.equal('somegppstring'); + expect(data.regs.ext.gpp_sid).to.eql([6, 7]); + }); }); describe('Endpoint & Impression Request Mode:', () => { diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 5df0e93d34e..e5151cf789c 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -1,7 +1,7 @@ import { config } from 'src/config.js'; -import { expect } from 'chai' -import { spec } from 'modules/yieldlabBidAdapter.js' -import { newBidder } from 'src/adapters/bidderFactory.js' +import { expect } from 'chai'; +import { spec } from 'modules/yieldlabBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const DEFAULT_REQUEST = () => ({ bidder: 'yieldlab', @@ -11,11 +11,11 @@ const DEFAULT_REQUEST = () => ({ targeting: { key1: 'value1', key2: 'value2', - notDoubleEncoded: 'value3,value4' + notDoubleEncoded: 'value3,value4', }, customParams: { extraParam: true, - foo: 'bar' + foo: 'bar', }, extId: 'abc', iabContent: { @@ -31,8 +31,8 @@ const DEFAULT_REQUEST = () => ({ cat: ['cat1', 'cat2,ppp', 'cat3|||//'], context: '7', keywords: ['k1,', 'k2..'], - live: '0' - } + live: '0', + }, }, bidderRequestId: '143346cf0f1731', auctionId: '2e41f65424c87c', @@ -43,8 +43,14 @@ const DEFAULT_REQUEST = () => ({ source: 'netid.de', uids: [{ id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - atype: 1 - }] + atype: 1, + }], + }, { + source: 'digitrust.de', + uids: [{ + id: 'd8aa10fa-d86c-451d-aad8-5f16162a9e64', + atype: 2, + }], }], schain: { ver: '1.0', @@ -53,32 +59,32 @@ const DEFAULT_REQUEST = () => ({ { asi: 'indirectseller.com', sid: '1', - hp: 1 + hp: 1, }, { asi: 'indirectseller2.com', name: 'indirectseller2 name with comma , and bang !', sid: '2', - hp: 1 - } - ] - } -}) + hp: 1, + }, + ], + }, +}); const VIDEO_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { mediaTypes: { video: { playerSize: [[640, 480]], - context: 'instream' - } - } -}) + context: 'instream', + }, + }, +}); const NATIVE_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { mediaTypes: { - native: {} - } -}) + native: {}, + }, +}); const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { params: { @@ -113,7 +119,7 @@ const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { name: 'bar', cattax: 532, cat: [1, 'foo', true], - domain: 'producer.test' + domain: 'producer.test', }, data: { id: 'foo', @@ -123,8 +129,8 @@ const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { value: 'bar', ext: { foo: { - bar: 'bar' - } + bar: 'bar', + }, }, }, { name: 'foo2', @@ -133,27 +139,27 @@ const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { test: { nums: { int: 123, - float: 123.123 + float: 123.123, }, bool: true, - string: 'foo2' - } - } + string: 'foo2', + }, + }, }], }, network: { id: 'foo', name: 'bar', - domain: 'network.test' + domain: 'network.test', }, channel: { id: 'bar', name: 'foo', - domain: 'channel.test' - } - } - } -}) + domain: 'channel.test', + }, + }, + }, +}); const RESPONSE = { advertiser: 'yieldlab', @@ -163,169 +169,173 @@ const RESPONSE = { price: 1, pid: 2222, adsize: '728x90', - adtype: 'BANNER' -} + adtype: 'BANNER', +}; const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { adtype: 'NATIVE', native: { link: { - url: 'https://www.yieldlab.de' + url: 'https://www.yieldlab.de', }, assets: [ { id: 1, title: { - text: 'This is a great headline' - } + text: 'This is a great headline', + }, }, { id: 2, img: { url: 'https://localhost:8080/yl-logo100x100.jpg', w: 100, - h: 100 - } + h: 100, + }, }, { id: 3, data: { - value: 'Native body value' - } - } + value: 'Native body value', + }, + }, ], imptrackers: [ 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', 'http://localhost:8080/md/1111/9efa4e76-2030-4f04-bb9f-322541f8d611?mdata=false&pvid=false&ids=x:1', - 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0' - ] - } -}) + 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0', + ], + }, +}); const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { - adtype: 'VIDEO' -}) + adtype: 'VIDEO', +}); const PVID_RESPONSE = Object.assign({}, VIDEO_RESPONSE, { - pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c' -}) + pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c', +}); const REQPARAMS = { json: true, - ts: 1234567890 -} + ts: 1234567890, +}; const REQPARAMS_GDPR = Object.assign({}, REQPARAMS, { gdpr: true, - consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA' -}) + consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', +}); const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { - iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0' -}) + iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0', +}); describe('yieldlabBidAdapter', () => { describe('instantiation from spec', () => { it('is working properly', () => { - const yieldlabBidAdapter = newBidder(spec) - expect(yieldlabBidAdapter.callBids).to.exist.and.to.be.a('function') - }) - }) + const yieldlabBidAdapter = newBidder(spec); + expect(yieldlabBidAdapter.callBids).to.exist.and.to.be.a('function'); + }); + }); describe('isBidRequestValid', () => { it('should return true when all required parameters are found', () => { const request = { params: { adslotId: '1111', - supplyId: '2222' - } - } - expect(spec.isBidRequestValid(request)).to.equal(true) - }) + supplyId: '2222', + }, + }; + expect(spec.isBidRequestValid(request)).to.equal(true); + }); it('should return false when required parameters are missing', () => { - expect(spec.isBidRequestValid({})).to.equal(false) - }) - }) + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); describe('buildRequests', () => { - const bidRequests = [DEFAULT_REQUEST()] + const bidRequests = [DEFAULT_REQUEST()]; describe('default functionality', () => { - let request + let request; before(() => { - request = spec.buildRequests(bidRequests) - }) + request = spec.buildRequests(bidRequests); + }); it('sends bid request to ENDPOINT via GET', () => { - expect(request.method).to.equal('GET') - }) + expect(request.method).to.equal('GET'); + }); it('returns a list of valid requests', () => { - expect(request.validBidRequests).to.eql(bidRequests) - }) + expect(request.validBidRequests).to.eql(bidRequests); + }); it('passes single-encoded targeting to bid request', () => { - expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') - }) + expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4'); + }); it('passes userids to bid request', () => { - expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg') - }) + expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg%2Cdigitrust.de%3Ad8aa10fa-d86c-451d-aad8-5f16162a9e64'); + }); + + it('passes atype to bid request', () => { + expect(request.url).to.include('atypes=netid.de%3A1%2Cdigitrust.de%3A2'); + }); it('passes extra params to bid request', () => { - expect(request.url).to.include('extraParam=true&foo=bar') - }) + expect(request.url).to.include('extraParam=true&foo=bar'); + }); it('passes unencoded schain string to bid request', () => { - expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') - }) + expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,'); + }); it('passes iab_content string to bid request', () => { - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') - }) + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0'); + }); it('passes correct size to bid request', () => { - expect(request.url).to.include('728x90') - }) + expect(request.url).to.include('728x90'); + }); it('passes external id to bid request', () => { - expect(request.url).to.include('id=abc') - }) - }) + expect(request.url).to.include('id=abc'); + }); + }); describe('iab_content handling', () => { const siteConfig = { ortb2: { site: { content: { - id: 'id_from_config' - } - } - } - } + id: 'id_from_config', + }, + }, + }, + }; beforeEach(() => { - config.setConfig(siteConfig) - }) + config.setConfig(siteConfig); + }); afterEach(() => { - config.resetConfig() - }) + config.resetConfig(); + }); it('generates iab_content string from bidder params', () => { - const request = spec.buildRequests(bidRequests) - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') - }) + const request = spec.buildRequests(bidRequests); + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0'); + }); it('generates iab_content string from first party data if not provided in bidder params', () => { - const requestWithoutIabContent = DEFAULT_REQUEST() - delete requestWithoutIabContent.params.iabContent + const requestWithoutIabContent = DEFAULT_REQUEST(); + delete requestWithoutIabContent.params.iabContent; - const request = spec.buildRequests([{...requestWithoutIabContent, ...siteConfig}]) - expect(request.url).to.include('iab_content=id%3Aid_from_config') - }) + const request = spec.buildRequests([{...requestWithoutIabContent, ...siteConfig}]); + expect(request.url).to.include('iab_content=id%3Aid_from_config'); + }); it('flattens the iabContent, encodes the values, joins the keywords into one value, and than encodes the iab_content request param ', () => { const expectedIabContentValue = encodeURIComponent( @@ -372,18 +382,18 @@ describe('yieldlabBidAdapter', () => { 'channel.id:bar,' + 'channel.name:foo,' + 'channel.domain:channel.test' - ) - const request = spec.buildRequests([IAB_REQUEST()], REQPARAMS) - expect(request.url).to.include('iab_content=' + expectedIabContentValue) - }) - }) + ); + const request = spec.buildRequests([IAB_REQUEST()], REQPARAMS); + expect(request.url).to.include('iab_content=' + expectedIabContentValue); + }); + }); it('passes unencoded schain string to bid request when complete == 0', () => { - const schainRequest = DEFAULT_REQUEST() + const schainRequest = DEFAULT_REQUEST(); schainRequest.schain.complete = 0; // - const request = spec.buildRequests([schainRequest]) - expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') - }) + const request = spec.buildRequests([schainRequest]); + expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,'); + }); it('passes encoded referer to bid request', () => { const refererRequest = spec.buildRequests(bidRequests, { @@ -392,123 +402,123 @@ describe('yieldlabBidAdapter', () => { numIframes: 0, reachedTop: true, page: 'https://www.yieldlab.de/test?with=querystring', - stack: ['https://www.yieldlab.de/test?with=querystring'] - } - }) + stack: ['https://www.yieldlab.de/test?with=querystring'], + }, + }); - expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') - }) + expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring'); + }); it('passes gdpr flag and consent if present', () => { const gdprRequest = spec.buildRequests(bidRequests, { gdprConsent: { consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', - gdprApplies: true - } - }) + gdprApplies: true, + }, + }); - expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA') - expect(gdprRequest.url).to.include('gdpr=true') - }) + expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(gdprRequest.url).to.include('gdpr=true'); + }); describe('sizes handling', () => { it('passes correct size to bid request for mediaType banner', () => { const bannerRequest = DEFAULT_REQUEST(); bannerRequest.mediaTypes = { banner: { - sizes: [[123, 456]] - } - } + sizes: [[123, 456]], + }, + }; // when mediaTypes is present it has precedence over the sizes field (728, 90) - let request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('sizes') - expect(request.url).to.include('123x456') - - bannerRequest.mediaTypes.banner.sizes = [123, 456] - request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('123x456') - - bannerRequest.mediaTypes.banner.sizes = [[123, 456], [320, 240]] - request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('123x456') - expect(request.url).to.include('320x240') - }) + let request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('sizes'); + expect(request.url).to.include('123x456'); + + bannerRequest.mediaTypes.banner.sizes = [123, 456]; + request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('123x456'); + + bannerRequest.mediaTypes.banner.sizes = [[123, 456], [320, 240]]; + request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('123x456'); + expect(request.url).to.include('320x240'); + }); it('passes correct sizes to bid request when mediaType is not present', () => { // information is taken from the top level sizes field const sizesRequest = DEFAULT_REQUEST(); - let request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('sizes') - expect(request.url).to.include('728x90') + let request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('sizes'); + expect(request.url).to.include('728x90'); - sizesRequest.sizes = [[728, 90]] - request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('728x90') + sizesRequest.sizes = [[728, 90]]; + request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('728x90'); - sizesRequest.sizes = [[728, 90], [320, 240]] - request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('728x90') - }) + sizesRequest.sizes = [[728, 90], [320, 240]]; + request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('728x90'); + }); it('does not pass the sizes parameter for mediaType video', () => { const videoRequest = VIDEO_REQUEST(); - let request = spec.buildRequests([videoRequest], REQPARAMS) - expect(request.url).to.not.include('sizes') - }) + let request = spec.buildRequests([videoRequest], REQPARAMS); + expect(request.url).to.not.include('sizes'); + }); it('does not pass the sizes parameter for mediaType native', () => { const nativeRequest = NATIVE_REQUEST(); - let request = spec.buildRequests([nativeRequest], REQPARAMS) - expect(request.url).to.not.include('sizes') - }) - }) - }) + let request = spec.buildRequests([nativeRequest], REQPARAMS); + expect(request.url).to.not.include('sizes'); + }); + }); + }); describe('interpretResponse', () => { - let bidRequest + let bidRequest; before(() => { - bidRequest = DEFAULT_REQUEST() - }) + bidRequest = DEFAULT_REQUEST(); + }); it('handles nobid responses', () => { - expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) - expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) - }) + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0); + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0); + }); it('should get correct bid response', () => { - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS}) - - expect(result[0].requestId).to.equal('2d925f27f5079f') - expect(result[0].cpm).to.equal(0.01) - expect(result[0].width).to.equal(728) - expect(result[0].height).to.equal(90) - expect(result[0].creativeId).to.equal('1111') - expect(result[0].dealId).to.equal(2222) - expect(result[0].currency).to.equal('EUR') - expect(result[0].netRevenue).to.equal(false) - expect(result[0].ttl).to.equal(300) - expect(result[0].referrer).to.equal('') - expect(result[0].meta.advertiserDomains).to.equal('yieldlab') - expect(result[0].ad).to.include('