diff --git a/integrationExamples/gpt/azerionedgeRtdProvider_example.html b/integrationExamples/gpt/azerionedgeRtdProvider_example.html new file mode 100644 index 00000000000..880fe5ed706 --- /dev/null +++ b/integrationExamples/gpt/azerionedgeRtdProvider_example.html @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + +

Azerion Edge RTD

+ +
+ +
+ + Segments: +
+ + diff --git a/integrationExamples/gpt/publir_hello_world.html b/integrationExamples/gpt/publir_hello_world.html new file mode 100644 index 00000000000..4763525408d --- /dev/null +++ b/integrationExamples/gpt/publir_hello_world.html @@ -0,0 +1,84 @@ + + + + + Prebid.js Banner gpt Example + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/libraries/appnexusUtils/anKeywords.js b/libraries/appnexusUtils/anKeywords.js index d6714dacc21..a6fa8d7a21e 100644 --- a/libraries/appnexusUtils/anKeywords.js +++ b/libraries/appnexusUtils/anKeywords.js @@ -78,7 +78,7 @@ export function convertKeywordStringToANMap(keyStr) { } /** - * @param {Array} kwarray: keywords as an array of strings + * @param {Array} kwarray keywords as an array of strings * @return {{}} appnexus-style keyword map */ function convertKeywordsToANMap(kwarray) { diff --git a/libraries/autoplayDetection/autoplay.js b/libraries/autoplayDetection/autoplay.js new file mode 100644 index 00000000000..b598e46cbd1 --- /dev/null +++ b/libraries/autoplayDetection/autoplay.js @@ -0,0 +1,42 @@ +let autoplayEnabled = null; + +/** + * Note: this function returns true if detection is not done yet. This is by design: if autoplay is not allowed, + * the call to video.play() will fail immediately, otherwise it may not terminate. + * @returns true if autoplay is not forbidden + */ +export const isAutoplayEnabled = () => autoplayEnabled !== false; + +// generated with: +// ask ChatGPT for a 160x90 black PNG image (1/8th the size of 720p) +// +// encode with: +// ffmpeg -i black_image_160x90.png -r 1 -c:v libx264 -bsf:v 'filter_units=remove_types=6' -pix_fmt yuv420p autoplay.mp4 +// this creates a 1 second long, 1 fps YUV 4:2:0 video encoded with H.264 without encoder details. +// +// followed by: +// echo "data:video/mp4;base64,$(base64 -i autoplay.mp4)" + +const autoplayVideoUrl = + 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAADxtZGF0AAAAMGWIhAAV//73ye/Apuvb3rW/k89I/Cy3PsIqP39atohOSV14BYa1heKCYgALQC5K4QAAAwZtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAD6AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACMHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAD6AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAoAAAAFoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAA+gAAAAAAAEAAAAAAahtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAEAAAABAAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFTbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABE3N0YmwAAACvc3RzZAAAAAAAAAABAAAAn2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAoABaAEgAAABIAAAAAAAAAAEVTGF2YzYwLjMxLjEwMiBsaWJ4MjY0AAAAAAAAAAAAAAAY//8AAAA1YXZjQwFkAAr/4QAYZ2QACqzZQo35IQAAAwABAAADAAIPEiWWAQAGaOvjyyLA/fj4AAAAABRidHJ0AAAAAAAAAaAAAAGgAAAAGHN0dHMAAAAAAAAAAQAAAAEAAEAAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAADQAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjYwLjE2LjEwMA=='; + +function startDetection() { + // we create an HTMLVideoElement muted and not displayed in which we try to play a one frame video + const videoElement = document.createElement('video'); + videoElement.src = autoplayVideoUrl; + videoElement.setAttribute('playsinline', 'true'); + videoElement.muted = true; + + videoElement + .play() + .then(() => { + autoplayEnabled = true; + videoElement.pause(); + }) + .catch(() => { + autoplayEnabled = false; + }); +} + +// starts detection as soon as this library is loaded +startDetection(); diff --git a/libraries/keywords/keywords.js b/libraries/keywords/keywords.js index 645c9c8d38f..b317bcf0c6b 100644 --- a/libraries/keywords/keywords.js +++ b/libraries/keywords/keywords.js @@ -6,7 +6,7 @@ const ORTB_KEYWORDS_PATHS = ['user.keywords'].concat( ); /** - * @param commaSeparatedKeywords: any number of either keyword arrays, or comma-separated keyword strings + * @param commaSeparatedKeywords any number of either keyword arrays, or comma-separated keyword strings * @returns an array with all unique keywords contained across all inputs */ export function mergeKeywords(...commaSeparatedKeywords) { diff --git a/libraries/objectGuard/objectGuard.js b/libraries/objectGuard/objectGuard.js index cf3d2f38256..784c3f1444d 100644 --- a/libraries/objectGuard/objectGuard.js +++ b/libraries/objectGuard/objectGuard.js @@ -2,6 +2,8 @@ import {isData, objectTransformer, sessionedApplies} from '../../src/activities/ import {deepAccess, deepClone, deepEqual, deepSetValue} from '../../src/utils.js'; /** + * @typedef {import('../src/activities/redactor.js').TransformationRuleDef} TransformationRuleDef + * @typedef {import('../src/adapters/bidderFactory.js').TransformationRule} TransformationRule * @typedef {Object} ObjectGuard * @property {*} obj a view on the guarded object * @property {function(): void} verify a function that checks for and rolls back disallowed changes to the guarded object diff --git a/libraries/objectGuard/ortbGuard.js b/libraries/objectGuard/ortbGuard.js index 7911b378c3d..62918d55548 100644 --- a/libraries/objectGuard/ortbGuard.js +++ b/libraries/objectGuard/ortbGuard.js @@ -9,6 +9,10 @@ import { import {objectGuard, writeProtectRule} from './objectGuard.js'; import {mergeDeep} from '../../src/utils.js'; +/** + * @typedef {import('./objectGuard.js').ObjectGuard} ObjectGuard + */ + function ortb2EnrichRules(isAllowed = isActivityAllowed) { return [ { diff --git a/libraries/uid1Eids/uid1Eids.js b/libraries/uid1Eids/uid1Eids.js new file mode 100644 index 00000000000..5bf3dde5c6c --- /dev/null +++ b/libraries/uid1Eids/uid1Eids.js @@ -0,0 +1,16 @@ +export const UID1_EIDS = { + 'tdid': { + source: 'adserver.org', + atype: 1, + getValue: function(data) { + if (data.id) { + return data.id; + } else { + return data; + } + }, + getUidExt: function(data) { + return {...{rtiPartner: 'TDID'}, ...data.ext} + } + } +} diff --git a/libraries/video/shared/parentModule.js b/libraries/video/shared/parentModule.js index 06c71ebd75b..b040f39bcb8 100644 --- a/libraries/video/shared/parentModule.js +++ b/libraries/video/shared/parentModule.js @@ -47,6 +47,7 @@ export function ParentModule(submoduleBuilder_) { } /** + * @typedef {import('../../../modules/videoModule/coreVideo.js').vendorSubmoduleDirectory} vendorSubmoduleDirectory * @typedef {Object} SubmoduleBuilder * @summary Instantiates submodules * @param {vendorSubmoduleDirectory} submoduleDirectory_ diff --git a/modules/.submodules.json b/modules/.submodules.json index 61d8c843d47..cfa98b5ab32 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -63,6 +63,7 @@ "airgridRtdProvider", "akamaiDapRtdProvider", "arcspanRtdProvider", + "azerionedgeRtdProvider", "blueconicRtdProvider", "brandmetricsRtdProvider", "browsiRtdProvider", diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index acd89ef72df..6e3c38e4e85 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -77,7 +77,7 @@ export const ORTB_VIDEO_PARAMS = { 'boxingallowed': (value) => isInteger(value), 'playbackmethod': (value) => isArrayOfNums(value), 'playbackend': (value) => isInteger(value), - 'delivery': (value) => isInteger(value), + 'delivery': (value) => isArrayOfNums(value), 'pos': (value) => isInteger(value), 'api': (value) => isArrayOfNums(value) }; diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index 0484c383762..881b1adfcc4 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -230,7 +230,14 @@ export const spec = { ortb: bidResponse.native }; } else { - result[ mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm; + if (mediaType === VIDEO) { + result.vastXml = bidResponse.adm; + if (bidResponse.nurl) { + result.vastUrl = bidResponse.nurl; + } + } else { + result.ad = bidResponse.adm; + } } if (!bid.renderer && mediaType === VIDEO && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index d6a4030057a..62e0ab29f8a 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -111,7 +111,8 @@ export const spec = { {code: 'didnadisplay'}, {code: 'qortex'}, {code: 'adpluto'}, - {code: 'headbidder'} + {code: 'headbidder'}, + {code: 'digiad'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 3f87476def7..6c268b2d382 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -2,6 +2,7 @@ import {getValue, formatQS, logError, deepAccess, isArray, getBidIdParameter} fr import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -33,11 +34,13 @@ export const OPENRTB = { let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { code: BIDDER_CODE, + gvlid: 1281, aliases: [ - {code: 'pixad'} + {code: 'pixad', gvlid: 1281} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -190,38 +193,45 @@ export const spec = { interpretResponse: (response, request) => { const body = response.body; const bidResponses = []; + if (body && body?.data && isArray(body.data)) { body.data.forEach(bid => { - const resbid = { - requestId: bid.id, - cpm: bid.price, - width: bid.width, - height: bid.height, - currency: body.cur || 'TRY', - netRevenue: true, - creativeId: bid.creative_id, - meta: { - model: bid.mime_type, - advertiserDomains: bid && bid.adomain ? bid.adomain : [] - }, - bidder: bid.bidder, - mediaType: bid.type, - ttl: 60 - }; - - if (resbid.mediaType === 'video' && isUrl(bid.party_tag)) { - resbid.vastUrl = bid.party_tag; - resbid.vastImpUrl = bid.iurl; - } else if (resbid.mediaType === 'video') { - resbid.vastXml = bid.party_tag; - resbid.vastImpUrl = bid.iurl; - } else if (resbid.mediaType === 'banner') { - resbid.ad = bid.party_tag; - } else if (resbid.mediaType === 'native') { - resbid.native = interpretNativeAd(bid.party_tag) - }; + const bidRequest = getAssociatedBidRequest(request.data.imp, bid); + if (bidRequest) { + const resbid = { + requestId: bid.id, + cpm: bid.price, + width: bid.width, + height: bid.height, + currency: body.cur || 'TRY', + netRevenue: true, + creativeId: bid.creative_id, + meta: { + model: bid.mime_type, + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, + bidder: bid.bidder, + mediaType: bid.type, + ttl: 60 + }; + + if (resbid.mediaType === 'video' && isUrl(bid.party_tag)) { + resbid.vastUrl = bid.party_tag; + } else if (resbid.mediaType === 'video') { + resbid.vastXml = bid.party_tag; + } else if (resbid.mediaType === 'banner') { + resbid.ad = bid.party_tag; + } else if (resbid.mediaType === 'native') { + resbid.native = interpretNativeAd(bid.party_tag) + }; + + const context = deepAccess(bidRequest, 'mediatype.context'); + if (resbid.mediaType === 'video' && context === 'outstream') { + resbid.renderer = createOutstreamVideoRenderer(bid); + } - bidResponses.push(resbid); + bidResponses.push(resbid); + } }); } return bidResponses; @@ -272,6 +282,40 @@ function isUrl(str) { } }; +function outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createOutstreamVideoRenderer(bid) { + const renderer = Renderer.install({ + id: bid.bidId, + url: RENDERER_URL, + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logError('Prebid Error calling setRender on renderer' + err); + } + + return renderer; +} + +function getAssociatedBidRequest(bidRequests, bid) { + for (const request of bidRequests) { + if (request.id === bid.id) { + return request; + } + } + return undefined; +} + function enrichSlotWithFloors(slot, bidRequest) { try { const slotFloors = {}; diff --git a/modules/adnowBidAdapter.js b/modules/adnowBidAdapter.js index 99f56df58b2..5083f4cc93d 100644 --- a/modules/adnowBidAdapter.js +++ b/modules/adnowBidAdapter.js @@ -5,7 +5,7 @@ import {includes} from '../src/polyfill.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adnow'; -const ENDPOINT = 'https://n.ads3-adnow.com/a'; +const ENDPOINT = 'https://n.nnowa.com/a'; /** * @typedef {object} CommonBidData diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 02dd7453be8..eb5f3c19dea 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -105,15 +105,16 @@ const storageTool = (function () { const getUsi = function (meta, ortb2, bidderRequest) { // Fetch user id from parameters. - const paramUsi = (bidderRequest.bids) ? bidderRequest.bids.find(bid => { - if (bid.params && bid.params.userId) return true - }).params.userId : false - let usi = (meta && meta.usi) ? meta.usi : false + for (let i = 0; i < (bidderRequest.bids || []).length; i++) { + const bid = bidderRequest.bids[i]; + if (bid.params && bid.params.userId) { + return bid.params.userId; + } + } if (ortb2 && ortb2.user && ortb2.user.id) { - usi = ortb2.user.id + return ortb2.user.id } - if (paramUsi) usi = paramUsi - return usi; + return (meta && meta.usi) ? meta.usi : false } const getSegmentsFromOrtb = function (ortb2) { diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index b48a7ec43b0..9f2810e13df 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -7,6 +7,23 @@ import {config} from '../src/config.js'; import {OUTSTREAM} from '../src/video.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').MediaType} MediaType + * @typedef {import('../src/adapters/bidderFactory.js').Site} Site + * @typedef {import('../src/adapters/bidderFactory.js').Device} Device + * @typedef {import('../src/adapters/bidderFactory.js').User} User + * @typedef {import('../src/adapters/bidderFactory.js').Banner} Banner + * @typedef {import('../src/adapters/bidderFactory.js').Video} Video + * @typedef {import('../src/adapters/bidderFactory.js').AdUnit} AdUnit + * @typedef {import('../src/adapters/bidderFactory.js').Imp} Imp + */ + const BIDDER_CODE = 'adot'; const ADAPTER_VERSION = 'v2.0.0'; const GVLID = 272; diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index bfcc56050fb..f19cf020ca8 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -134,10 +134,9 @@ export const spec = { */ onBidWon: (bid) => { logInfo('onBidWon', bid); - const bidString = JSON.stringify(bid); - let copyOfBid = JSON.parse(bidString); - delete copyOfBid.ad; - const shortBidString = JSON.stringify(bid); + let copyOfBid = { ...bid } + delete copyOfBid.ad + const shortBidString = JSON.stringify(copyOfBid); const encodedBuf = window.btoa(shortBidString); let params = { diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js index 1da75f2063d..2dab76b7862 100644 --- a/modules/adriverIdSystem.js +++ b/modules/adriverIdSystem.js @@ -42,7 +42,6 @@ export const adriverIdSubmodule = { * performs action to obtain id and return a value in the callback's response argument * @function * @param {SubmoduleConfig} [config] - * @param {ConsentData} [consentData] * @returns {IdResponse|undefined} */ getId(config) { diff --git a/modules/adspiritBidAdapter.js b/modules/adspiritBidAdapter.js new file mode 100644 index 00000000000..c39ceca8600 --- /dev/null +++ b/modules/adspiritBidAdapter.js @@ -0,0 +1,124 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; + +const RTB_URL = '/rtb/getbid.php?rtbprovider=prebid'; +const SCRIPT_URL = '/adasync.min.js'; + +export const spec = { + + code: 'adspirit', + aliases: ['twiago'], + supportedMediaTypes: [BANNER, NATIVE], + + isBidRequestValid: function (bid) { + let host = spec.getBidderHost(bid); + if (!host || !bid.params.placementId) { + return false; + } + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let requests = []; + for (let i = 0; i < validBidRequests.length; i++) { + let bidRequest = validBidRequests[i]; + bidRequest.adspiritConId = spec.genAdConId(bidRequest); + let reqUrl = spec.getBidderHost(bidRequest); + let placementId = utils.getBidIdParameter('placementId', bidRequest.params); + reqUrl = '//' + reqUrl + RTB_URL + '&pid=' + placementId + + '&ref=' + encodeURIComponent(bidderRequest.refererInfo.topmostLocation) + + '&scx=' + (screen.width) + + '&scy=' + (screen.height) + + '&wcx=' + (window.innerWidth || document.documentElement.clientWidth) + + '&wcy=' + (window.innerHeight || document.documentElement.clientHeight) + + '&async=' + bidRequest.adspiritConId + + '&t=' + Math.round(Math.random() * 100000); + + let data = {}; + + if (bidderRequest && bidderRequest.gdprConsent) { + const gdprConsentString = bidderRequest.gdprConsent.consentString; + reqUrl += '&gdpr=' + encodeURIComponent(gdprConsentString); + } + + if (bidRequest.schain && bidderRequest.schain) { + data.schain = bidRequest.schain; + } + + requests.push({ + method: 'GET', + url: reqUrl, + data: data, + bidRequest: bidRequest + }); + } + return requests; + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + let bidObj = bidRequest.bidRequest; + + if (!serverResponse || !serverResponse.body || !bidObj) { + utils.logWarn(`No valid bids from ${spec.code} bidder!`); + return []; + } + + let adData = serverResponse.body; + let cpm = adData.cpm; + + if (!cpm) { + return []; + } + + let host = spec.getBidderHost(bidObj); + + const bidResponse = { + requestId: bidObj.bidId, + cpm: cpm, + width: adData.w, + height: adData.h, + creativeId: bidObj.params.placementId, + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: bidObj && bidObj.adomain ? bidObj.adomain : [] + } + }; + + if ('mediaTypes' in bidObj && 'native' in bidObj.mediaTypes) { + bidResponse.native = { + title: adData.title, + body: adData.body, + cta: adData.cta, + image: { url: adData.image }, + clickUrl: adData.click, + impressionTrackers: [adData.view] + }; + bidResponse.mediaType = NATIVE; + } else { + let adm = '' + adData.adm; + bidResponse.ad = adm; + bidResponse.mediaType = BANNER; + } + + bidResponses.push(bidResponse); + return bidResponses; + }, + getBidderHost: function (bid) { + if (bid.bidder === 'adspirit') { + return utils.getBidIdParameter('host', bid.params); + } + if (bid.bidder === 'twiago') { + return 'a.twiago.com'; + } + return null; + }, + + genAdConId: function (bid) { + return bid.bidder + Math.round(Math.random() * 100000); + } +}; + +registerBidder(spec); diff --git a/modules/adspiritBidAdapter.md b/modules/adspiritBidAdapter.md new file mode 100644 index 00000000000..698ed9b4a0e --- /dev/null +++ b/modules/adspiritBidAdapter.md @@ -0,0 +1,66 @@ + # Overview + + ``` +Module Name: Adspirit Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@adspirit.de + +``` +# Description + +Connects to Adspirit exchange for bids. + +Each adunit with `adspirit` adapter has to have `placementId` and `host`. + + +### Supported Features; + +1. Media Types: Banner & native +2. Multi-format: adUnits +3. Schain module +4. Advertiser domains + + +## Sample Banner Ad Unit + ```javascript + var adUnits = [ + { + code: 'display-div', + + mediaTypes: { + banner: { + sizes: [[300, 250]] //a display size + } + }, + + bids: [ + { + bidder: "adspirit", + params: { + placementId: '7', //Please enter your placementID + host: 'test.adspirit.de' //your host details from Adspirit + } + } + ] + } + ]; + +``` + + +### Privacy Policies + +General Data Protection Regulation(GDPR) is supported by default. + +Complete information on this URL-- https://support.adspirit.de/hc/en-us/categories/115000453312-General + + +### CMP (Consent Management Provider) +CMP stands for Consent Management Provider. In simple terms, this is a service provider that obtains and processes the consent of the user, makes it available to the advertisers and, if necessary, logs it for later control. We recommend using a provider with IAB certification or CMP based on the IAB CMP Framework. A list of IAB CMPs can be found at https://iabeurope.eu/cmp-list/. AdSpirit recommends the use of www.consentmanager.de . + +### List of functions that require consent + +Please visit our page- https://support.adspirit.de/hc/en-us/articles/360014631659-List-of-functions-that-require-consent + + + diff --git a/modules/adstirBidAdapter.js b/modules/adstirBidAdapter.js index 4b22d568785..a0c67ddac7e 100644 --- a/modules/adstirBidAdapter.js +++ b/modules/adstirBidAdapter.js @@ -36,6 +36,7 @@ export const spec = { topurl: config.getConfig('pageUrl') ? false : bidderRequest.refererInfo.reachedTop, }, sua, + user: utils.deepAccess(r, 'ortb2.user', null), gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies', false), usp: (bidderRequest.uspConsent || '1---') !== '1---', eids: utils.deepAccess(r, 'userIdAsEids', []), diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index cadba499b5c..a95b9ed5652 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -9,6 +9,7 @@ import {chunk} from '../libraries/chunk/chunk.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ const subdomainSuffixes = ['', 1, 2]; @@ -119,7 +120,7 @@ export const spec = { /** * Unpack the response from the server into a list of bids * @param serverResponse - * @param bidderRequest + * @param adapterRequest * @return {Bid[]} An array of bids which were nested inside the server */ interpretResponse: function (serverResponse, { adapterRequest }) { diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 9bc24b11ac3..ad1c0af039e 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -8,6 +8,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ const VERSION = '1.0'; @@ -62,7 +63,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {bidRequests} - bidRequests.bids[] is an array of AdUnits and bids + * @param {BidRequest} bidRequests is an array of AdUnits and bids + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 2184544807e..079628c88fc 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -105,7 +105,7 @@ function init(rtdConfig, userConsent) { /** * Real-time data retrieval from AirGrid - * @param {Object} reqBidsConfigObj + * @param {Object} bidConfig * @param {function} onDone * @param {Object} rtdConfig * @param {Object} userConsent diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index f0bb7eb3a6c..0bd53b2a91f 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -12,6 +12,10 @@ import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/ut import { loadExternalScript } from '../src/adloader.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; const MODULE_CODE = 'akamaidap'; @@ -44,9 +48,8 @@ function mergeLazy(target, source) { /** * Add real-time data & merge segments. - * @param {Object} ortb2 destionation object to merge RTD into + * @param {Object} ortb2 destination object to merge RTD into * @param {Object} rtd - * @param {Object} rtdConfig */ export function addRealTimeData(ortb2, rtd) { logInfo('DEBUG(addRealTimeData) - ENTER'); @@ -60,7 +63,7 @@ export function addRealTimeData(ortb2, rtd) { /** * Real-time data retrieval from Audigent - * @param {Object} reqBidsConfigObj + * @param {Object} bidConfig * @param {function} onDone * @param {Object} rtdConfig * @param {Object} userConsent diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 834df134c2e..dadbdb72e95 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -327,7 +327,7 @@ export function validateGeoObject(geo) { * Get bid floor from Price Floors Module * * @param {Object} bid - * @returns {float||null} + * @returns {?number} */ function getBidFloor(bid) { if (!isFn(bid.getFloor)) { diff --git a/modules/appierBidAdapter.js b/modules/appierBidAdapter.js index fa314f0bd5f..cf89aeefffa 100644 --- a/modules/appierBidAdapter.js +++ b/modules/appierBidAdapter.js @@ -37,7 +37,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {bidRequests[]} - an array of bids + * @param {object} bidRequests - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 704cffefb39..a4a6c78566e 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -1,32 +1,20 @@ -import { - _each, - deepAccess, - deepSetValue, - getDNT, - inIframe, - isArray, - isFn, - logWarn, - parseSizesInput -} 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 {deepAccess, deepSetValue} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'aso'; const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; const DEFAULT_SERVER_PATH = '/prebid/bidder'; -const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const VERSION = '$prebid.version$_1.1'; +const DEFAULT_CURRENCY = 'USD'; +const VERSION = '$prebid.version$_2.0'; const TTL = 300; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], aliases: [ {code: 'bcmint'}, {code: 'bidgency'} @@ -36,91 +24,30 @@ export const spec = { return !!bid.params && !!bid.params.zone; }, - buildRequests: (validBidRequests, bidderRequest) => { - let serverRequests = []; + buildRequests: (bidRequests, bidderRequest) => { + let requests = []; - _each(validBidRequests, bidRequest => { - const payload = createBasePayload(bidRequest, bidderRequest); - - const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); - const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); - - let imp; - - if (bannerParams && videoParams) { - logWarn('Please note, multiple mediaTypes are not supported. The only banner will be used.') - } - - if (bannerParams) { - imp = createBannerImp(bidRequest, bannerParams) - } else if (videoParams) { - imp = createVideoImp(bidRequest, videoParams) - } - - if (imp) { - payload.imp.push(imp); - } else { - return; - } - - serverRequests.push({ + bidRequests.forEach(bid => { + const data = converter.toORTB({bidRequests: [bid], bidderRequest}); + requests.push({ method: 'POST', - url: getEndpoint(bidRequest), - data: payload, + url: getEndpoint(bid), + data, options: { withCredentials: true, crossOrigin: true }, - bidRequest: bidRequest - }); + bidderRequest + }) }); - - return serverRequests; + return requests; }, - interpretResponse: (serverResponse, {bidRequest}) => { - const response = serverResponse && serverResponse.body; - - if (!response) { - return []; - } - - const serverBids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); - const serverBid = serverBids[0]; - - let bids = []; - - const bid = { - requestId: serverBid.impid, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - ttl: TTL, - creativeId: serverBid.crid, - netRevenue: true, - currency: response.cur, - mediaType: bidRequest.mediaType, - meta: { - mediaType: bidRequest.mediaType, - advertiserDomains: serverBid.adomain ? serverBid.adomain : [] - } - }; - - if (bid.mediaType === BANNER) { - bid.ad = serverBid.adm; - } else if (bid.mediaType === VIDEO) { - bid.vastXml = serverBid.adm; - if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') { - bid.adResponse = { - content: bid.vastXml, - }; - bid.renderer = createRenderer(bidRequest, OUTSTREAM_RENDERER_URL); - } + interpretResponse: (response, request) => { + if (response.body) { + return converter.fromORTB({response: response.body, request: request.data}).bids; } - - bids.push(bid); - - return bids; + return []; }, getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { @@ -141,16 +68,25 @@ export const spec = { query = tryAppendQueryString(query, 'us_privacy', uspConsent); } - _each(serverResponses, resp => { + if (query.slice(-1) === '&') { + query = query.slice(0, -1); + } + + serverResponses.forEach(resp => { const userSyncs = deepAccess(resp, 'body.ext.user_syncs'); if (!userSyncs) { return; } - _each(userSyncs, us => { + userSyncs.forEach(us => { + let url = us.url; + if (query) { + url = url + (url.indexOf('?') === -1 ? '?' : '&') + query; + } + urls.push({ type: us.type, - url: us.url + (query ? '?' + query : '') + url: url }); }); }); @@ -160,123 +96,50 @@ export const spec = { } }; -function outstreamRender(bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bid.width, bid.height], - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }); - }); -} - -function createRenderer(bid, url) { - const renderer = Renderer.install({ - id: bid.bidId, - url: url, - loaded: false, - config: deepAccess(bid, 'renderer.options'), - adUnitCode: bid.adUnitCode - }); - renderer.setRender(outstreamRender); - return renderer; -} - -function getUrlsInfo(bidderRequest) { - const {page, domain, ref} = bidderRequest.refererInfo; - return { - // TODO: do the fallbacks make sense here? - page: page || bidderRequest.refererInfo?.topmostLocation, - referrer: ref || '', - domain: domain || parseDomain(bidderRequest?.refererInfo?.topmostLocation) - } -} - -function getSize(paramSizes) { - const parsedSizes = parseSizesInput(paramSizes); - const sizes = parsedSizes.map(size => { - const [width, height] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return {w, h}; - }); - - return sizes[0] || null; -} - -function getBidFloor(bidRequest, size) { - if (!isFn(bidRequest.getFloor)) { - return null; - } - - const bidFloor = bidRequest.getFloor({ - mediaType: bidRequest.mediaType, - size: size ? [size.w, size.h] : '*' - }); - - if (!isNaN(bidFloor.floor)) { - return bidFloor; - } - - return null; -} - -function createBaseImp(bidRequest, size) { - const imp = { - id: bidRequest.bidId, - tagid: bidRequest.adUnitCode, - secure: 1 - }; - - const bidFloor = getBidFloor(bidRequest, size); - if (bidFloor !== null) { - imp.bidfloor = bidFloor.floor; - imp.bidfloorcur = bidFloor.currency; - } +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: TTL + }, - return imp; -} + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); -function createBannerImp(bidRequest, bannerParams) { - bidRequest.mediaType = BANNER; + imp.tagid = bidRequest.adUnitCode; + imp.secure = Number(window.location.protocol === 'https:'); + return imp; + }, - const size = getSize(bannerParams.sizes); - const imp = createBaseImp(bidRequest, size); + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); - imp.banner = { - w: size.w, - h: size.h, - topframe: inIframe() ? 0 : 1 - } + if (bidderRequest.gdprConsent) { + const consentsIds = getConsentsIds(bidderRequest.gdprConsent); + if (consentsIds) { + deepSetValue(request, 'user.ext.consents', consentsIds); + } + } - return imp; -} + if (!request.cur) { + request.cur = [DEFAULT_CURRENCY]; + } -function createVideoImp(bidRequest, videoParams) { - bidRequest.mediaType = VIDEO; - const size = getSize(videoParams.playerSize); - const imp = createBaseImp(bidRequest, size); + return request; + }, - imp.video = { - mimes: videoParams.mimes, - minduration: videoParams.minduration, - startdelay: videoParams.startdelay, - linearity: videoParams.linearity, - maxduration: videoParams.maxduration, - skip: videoParams.skip, - protocols: videoParams.protocols, - skipmin: videoParams.skipmin, - api: videoParams.api - } + bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.prebid.type'); + return buildBidResponse(bid, context); + }, - if (size) { - imp.video.w = size.w; - imp.video.h = size.h; + overrides: { + request: { + // We don't need extra data + gdprAddtlConsent(setAddtlConsent, ortbRequest, bidderRequest) { + } + } } - - return imp; -} +}); function getEndpoint(bidRequest) { const serverUrl = bidRequest.params.server || DEFAULT_SERVER_URL; @@ -287,7 +150,7 @@ function getConsentsIds(gdprConsent) { const consents = deepAccess(gdprConsent, 'vendorData.purpose.consents', []); let consentsIds = []; - Object.keys(consents).forEach(function (key) { + Object.keys(consents).forEach(key => { if (consents[key] === true) { consentsIds.push(key); } @@ -296,61 +159,4 @@ function getConsentsIds(gdprConsent) { return consentsIds.join(','); } -function createBasePayload(bidRequest, bidderRequest) { - const urlsInfo = getUrlsInfo(bidderRequest); - - const payload = { - id: bidRequest.bidId, - at: 1, - tmax: bidderRequest.timeout, - site: { - id: urlsInfo.domain, - domain: urlsInfo.domain, - page: urlsInfo.page, - ref: urlsInfo.referrer - }, - device: { - dnt: getDNT() ? 1 : 0, - h: window.innerHeight, - w: window.innerWidth, - }, - imp: [], - ext: {}, - user: {} - }; - - if (bidRequest.params.attr) { - deepSetValue(payload, 'site.ext.attr', bidRequest.params.attr); - } - - if (bidderRequest.gdprConsent) { - deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - const consentsIds = getConsentsIds(bidderRequest.gdprConsent); - if (consentsIds) { - deepSetValue(payload, 'user.ext.consents', consentsIds); - } - deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); - } - - if (bidderRequest.uspConsent) { - deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (config.getConfig('coppa')) { - deepSetValue(payload, 'regs.coppa', 1); - } - - const eids = deepAccess(bidRequest, 'userIdAsEids'); - if (eids && eids.length) { - deepSetValue(payload, 'user.ext.eids', eids); - } - - const schainData = deepAccess(bidRequest, 'schain.nodes'); - if (isArray(schainData) && schainData.length > 0) { - deepSetValue(payload, 'source.ext.schain', bidRequest.schain); - } - - return payload; -} - registerBidder(spec); diff --git a/modules/asoBidAdapter.md b/modules/asoBidAdapter.md index f187389c5b5..ebf5cfd4614 100644 --- a/modules/asoBidAdapter.md +++ b/modules/asoBidAdapter.md @@ -17,7 +17,6 @@ For more information, please visit [Adserver.Online](https://adserver.online). | Name | Scope | Description | Example | Type | |-----------|----------|-------------------------|------------------------|------------| | `zone` | required | Zone ID | `73815` | `Integer` | -| `attr` | optional | Custom targeting params | `{foo: ["a", "b"]}` | `Object` | | `server` | optional | Custom bidder endpoint | `https://endpoint.url` | `String` | # Test parameters for banner @@ -49,6 +48,9 @@ var videoAdUnit = [ code: 'video1', mediaTypes: { video: { + mimes: [ + "video/mp4" + ], playerSize: [[640, 480]], context: 'instream' // or 'outstream' } @@ -69,7 +71,6 @@ The Adserver.Online Bid Adapter expects Prebid Cache (for video) to be enabled. ``` pbjs.setConfig({ - usePrebidCache: true, cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache' } diff --git a/modules/astraoneBidAdapter.js b/modules/astraoneBidAdapter.js index 9645b8e68bd..216257fb7bc 100644 --- a/modules/astraoneBidAdapter.js +++ b/modules/astraoneBidAdapter.js @@ -6,6 +6,7 @@ import { BANNER } from '../src/mediaTypes.js' * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests */ const BIDDER_CODE = 'astraone'; @@ -100,7 +101,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} validBidRequests an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests(validBidRequests, bidderRequest) { diff --git a/modules/automatadAnalyticsAdapter.js b/modules/automatadAnalyticsAdapter.js index 7d7bd8cb34c..436418e7597 100644 --- a/modules/automatadAnalyticsAdapter.js +++ b/modules/automatadAnalyticsAdapter.js @@ -14,7 +14,7 @@ import { config } from '../src/config.js' const ADAPTER_CODE = 'automatadAnalytics' const trialCountMilsMapping = [1500, 3000, 5000, 10000]; -var isLoggingEnabled; var queuePointer = 0; var retryCount = 0; var timer = null; var __atmtdAnalyticsQueue = []; +var isLoggingEnabled; var queuePointer = 0; var retryCount = 0; var timer = null; var __atmtdAnalyticsQueue = []; var qBeingUsed; var qTraversalComplete; const prettyLog = (level, text, isGroup = false, cb = () => {}) => { if (self.isLoggingEnabled === undefined) { @@ -134,6 +134,9 @@ const processEvents = () => { if (trialCountMilsMapping[self.retryCount]) self.prettyLog('warn', `Adapter failed to process event as aggregator has not loaded. Retrying in ${trialCountMilsMapping[self.retryCount]}ms ...`); setTimeout(self.processEvents, trialCountMilsMapping[self.retryCount]) self.retryCount = self.retryCount + 1 + } else { + self.qBeingUsed = false + self.qTraversalComplete = true } } @@ -142,22 +145,18 @@ const addGPTHandlers = () => { googletag.cmd = googletag.cmd || [] googletag.cmd.push(() => { googletag.pubads().addEventListener('slotRenderEnded', (event) => { - if (window.atmtdAnalytics && window.atmtdAnalytics.slotRenderEndedGPTHandler) { - if (window.__atmtdAggregatorFirstAuctionInitialized === true) { - window.atmtdAnalytics.slotRenderEndedGPTHandler(event) - return; - } + if (window.atmtdAnalytics && window.atmtdAnalytics.slotRenderEndedGPTHandler && !self.qBeingUsed) { + window.atmtdAnalytics.slotRenderEndedGPTHandler(event) + return; } self.__atmtdAnalyticsQueue.push(['slotRenderEnded', event]) self.prettyLog(`warn`, `Aggregator not initialised at auctionInit, exiting slotRenderEnded handler and pushing to que instead`) }) googletag.pubads().addEventListener('impressionViewable', (event) => { - if (window.atmtdAnalytics && window.atmtdAnalytics.impressionViewableHandler) { - if (window.__atmtdAggregatorFirstAuctionInitialized === true) { - window.atmtdAnalytics.impressionViewableHandler(event) - return; - } + if (window.atmtdAnalytics && window.atmtdAnalytics.impressionViewableHandler && !self.qBeingUsed) { + window.atmtdAnalytics.impressionViewableHandler(event) + return; } self.__atmtdAnalyticsQueue.push(['impressionViewable', event]) self.prettyLog(`warn`, `Aggregator not initialised at auctionInit, exiting impressionViewable handler and pushing to que instead`) @@ -167,6 +166,7 @@ const addGPTHandlers = () => { const initializeQueue = () => { self.__atmtdAnalyticsQueue.push = (args) => { + self.qBeingUsed = true Array.prototype.push.apply(self.__atmtdAnalyticsQueue, [args]); if (timer) { clearTimeout(timer); @@ -196,9 +196,10 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { }, track({eventType, args}) { + const shouldNotPushToQueue = !self.qBeingUsed switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: - if (window.atmtdAnalytics && window.atmtdAnalytics.auctionInitHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.auctionInitHandler && shouldNotPushToQueue) { self.prettyLog('status', 'Aggregator loaded, initialising auction through handlers'); window.atmtdAnalytics.auctionInitHandler(args); } else { @@ -207,7 +208,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.BID_REQUESTED: - if (window.atmtdAnalytics && window.atmtdAnalytics.bidRequestedHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.bidRequestedHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidRequestedHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -215,7 +216,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.BID_REJECTED: - if (window.atmtdAnalytics && window.atmtdAnalytics.bidRejectedHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.bidRejectedHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidRejectedHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -223,7 +224,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.BID_RESPONSE: - if (window.atmtdAnalytics && window.atmtdAnalytics.bidResponseHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.bidResponseHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidResponseHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -231,7 +232,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.BIDDER_DONE: - if (window.atmtdAnalytics && window.atmtdAnalytics.bidderDoneHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.bidderDoneHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidderDoneHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -239,7 +240,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.BID_WON: - if (window.atmtdAnalytics && window.atmtdAnalytics.bidWonHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.bidWonHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidWonHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -247,7 +248,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.NO_BID: - if (window.atmtdAnalytics && window.atmtdAnalytics.noBidHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.noBidHandler && shouldNotPushToQueue) { window.atmtdAnalytics.noBidHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -255,7 +256,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.AUCTION_DEBUG: - if (window.atmtdAnalytics && window.atmtdAnalytics.auctionDebugHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.auctionDebugHandler && shouldNotPushToQueue) { window.atmtdAnalytics.auctionDebugHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -263,7 +264,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { } break; case CONSTANTS.EVENTS.BID_TIMEOUT: - if (window.atmtdAnalytics && window.atmtdAnalytics.bidderTimeoutHandler) { + if (window.atmtdAnalytics && window.atmtdAnalytics.bidderTimeoutHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidderTimeoutHandler(args); } else { self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); @@ -304,7 +305,7 @@ atmtdAdapter.enableAnalytics = function (configuration) { atmtdAdapter.originEnableAnalytics(configuration) }; -/// /////////// ADAPTER REGISTRATION ////////////// +/// /////////// ADAPTER REGISTRATION ///////////// adapterManager.registerAnalyticsAdapter({ adapter: atmtdAdapter, @@ -319,7 +320,15 @@ export var self = { prettyLog, queuePointer, retryCount, - isLoggingEnabled + isLoggingEnabled, + qBeingUsed, + qTraversalComplete +} + +window.__atmtdAnalyticsGlobalObject = { + q: self.__atmtdAnalyticsQueue, + qBeingUsed: self.qBeingUsed, + qTraversalComplete: self.qTraversalComplete } export default atmtdAdapter; diff --git a/modules/azerionedgeRtdProvider.js b/modules/azerionedgeRtdProvider.js new file mode 100644 index 00000000000..a162ce074aa --- /dev/null +++ b/modules/azerionedgeRtdProvider.js @@ -0,0 +1,143 @@ +/** + * This module adds the Azerion provider to the real time data module of prebid. + * + * The {@link module:modules/realTimeData} module is required + * @module modules/azerionedgeRtdProvider + * @requires module:modules/realTimeData + */ +import { submodule } from '../src/hook.js'; +import { mergeDeep } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; + +/** + * @typedef {import('./rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + +const REAL_TIME_MODULE = 'realTimeData'; +const SUBREAL_TIME_MODULE = 'azerionedge'; +export const STORAGE_KEY = 'ht-pa-v1-a'; + +export const storage = getStorageManager({ + moduleType: MODULE_TYPE_RTD, + moduleName: SUBREAL_TIME_MODULE, +}); + +/** + * Get script url to load + * + * @param {Object} config + * + * @return {String} + */ +function getScriptURL(config) { + const VERSION = 'v1'; + const key = config.params?.key; + const publisherPath = key ? `${key}/` : ''; + return `https://edge.hyth.io/js/${VERSION}/${publisherPath}azerion-edge.min.js`; +} + +/** + * Attach script tag to DOM + * + * @param {Object} config + * + * @return {void} + */ +export function attachScript(config) { + const script = getScriptURL(config); + loadExternalScript(script, SUBREAL_TIME_MODULE, () => { + if (typeof window.azerionPublisherAudiences === 'function') { + window.azerionPublisherAudiences(config.params?.process || {}); + } + }); +} + +/** + * Fetch audiences info from localStorage. + * + * @return {Array} Audience ids. + */ +export function getAudiences() { + try { + const data = storage.getDataFromLocalStorage(STORAGE_KEY); + return JSON.parse(data).map(({ id }) => id); + } catch (_) { + return []; + } +} + +/** + * Pass audience data to configured bidders, using ORTB2 + * + * @param {Object} reqBidsConfigObj + * @param {Object} config + * @param {Array} audiences + * + * @return {void} + */ +export function setAudiencesToBidders(reqBidsConfigObj, config, audiences) { + const defaultBidders = ['improvedigital']; + const bidders = config.params?.bidders || defaultBidders; + bidders.forEach((bidderCode) => + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { + [bidderCode]: { + user: { + data: [ + { + name: 'azerionedge', + ext: { segtax: 4 }, + segment: audiences.map((id) => ({ id })), + }, + ], + }, + }, + }) + ); +} + +/** + * Module initialisation. + * + * @param {Object} config + * @param {Object} userConsent + * + * @return {boolean} + */ +function init(config, userConsent) { + attachScript(config); + return true; +} + +/** + * Real-time user audiences retrieval + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} config + * @param {Object} userConsent + * + * @return {void} + */ +export function getBidRequestData( + reqBidsConfigObj, + callback, + config, + userConsent +) { + const audiences = getAudiences(); + if (audiences.length > 0) { + setAudiencesToBidders(reqBidsConfigObj, config, audiences); + } + callback(); +} + +/** @type {RtdSubmodule} */ +export const azerionedgeSubmodule = { + name: SUBREAL_TIME_MODULE, + init: init, + getBidRequestData: getBidRequestData, +}; + +submodule(REAL_TIME_MODULE, azerionedgeSubmodule); diff --git a/modules/azerionedgeRtdProvider.md b/modules/azerionedgeRtdProvider.md new file mode 100644 index 00000000000..2849bef3f63 --- /dev/null +++ b/modules/azerionedgeRtdProvider.md @@ -0,0 +1,112 @@ +--- +layout: page_v2 +title: azerion edge RTD Provider +display_name: Azerion Edge RTD Provider +description: Client-side contextual cookieless audiences. +page_type: module +module_type: rtd +module_code: azerionedgeRtdProvider +enable_download: true +vendor_specific: true +sidebarType: 1 +--- + +# Azerion Edge RTD Provider + +Client-side contextual cookieless audiences. + +Azerion Edge RTD module helps publishers to capture users' interest +audiences on their site, and attach these into the bid request. + +Maintainer: [azerion.com](https://www.azerion.com/) + +{:.no_toc} + +- TOC + {:toc} + +## Integration + +Compile the Azerion Edge RTD module (`azerionedgeRtdProvider`) into your Prebid build, +along with the parent RTD Module (`rtdModule`): + +```bash +gulp build --modules=rtdModule,azerionedgeRtdProvider +``` + +Set configuration via `pbjs.setConfig`. + +```js +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: 'azerionedge', + waitForIt: true, + params: { + key: '', + bidders: ['improvedigital'], + process: {} + } + } + ] + } + ... +} +``` + +### Parameter Description + +{: .table .table-bordered .table-striped } +| Name | Type | Description | Notes | +| :--- | :------- | :------------------ | :--------------- | +| name | `String` | RTD sub module name | Always "azerionedge" | +| waitForIt | `Boolean` | Required to ensure that the auction is delayed for the module to respond. | Optional. Defaults to false but recommended to true. | +| params.key | `String` | Publisher partner specific key | Optional | +| params.bidders | `Array` | Bidders with which to share segment information | Optional. Defaults to "improvedigital". | +| params.process | `Object` | Configuration for the Azerion Edge script. | Optional. Defaults to `{}`. | + +## Context + +As all data collection is on behalf of the publisher and based on the consent the publisher has +received from the user, this module does not require a TCF vendor configuration. Consent is +provided to the module when the user gives the relevant permissions on the publisher website. + +As Prebid.js utilizes TCF vendor consent for the RTD module to load, the module needs to be labeled +within the Vendor Exceptions. + +### Instructions + +If the Prebid GDPR enforcement is enabled, the module should be labeled +as exception, as shown below: + +```js +[ + { + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ["azerionedge"] + }, + ... +] +``` + +## Testing + +To view an example: + +```bash +gulp serve-fast --modules=rtdModule,azerionedgeRtdProvider +``` + +Access [http://localhost:9999/integrationExamples/gpt/azerionedgeRtdProvider_example.html](http://localhost:9999/integrationExamples/gpt/azerionedgeRtdProvider_example.html) +in your browser. + +Run the unit tests: + +```bash +npm test -- --file "test/spec/modules/azerionedgeRtdProvider_spec.js" +``` diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 89289654b32..0b2a965448b 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -12,6 +12,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + */ + const BIDDER_CODE = 'beop'; const ENDPOINT_URL = 'https://hb.beop.io/bid'; const TCF_VENDOR_ID = 666; @@ -25,7 +31,7 @@ export const spec = { /** * Test if the bid request is valid. * - * @param {bid} : The Bid params + * @param {Bid} bid The Bid params * @return boolean true if the bid request is valid (aka contains a valid accountId or networkId and is open for BANNER), false otherwise. */ isBidRequestValid: function(bid) { @@ -41,8 +47,8 @@ export const spec = { /** * Create a BeOp server request from a list of BidRequest * - * @param {validBidRequests[], ...} : The array of validated bidRequests - * @param {... , bidderRequest} : Common params for each bidRequests + * @param {validBidRequests} validBidRequests The array of validated bidRequests + * @param {BidderRequest} bidderRequest Common params for each bidRequests * @return ServerRequest Info describing the request to the BeOp's server */ buildRequests: function(validBidRequests, bidderRequest) { diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index 4a953875d99..d2010f22e1a 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -6,6 +6,7 @@ import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync @@ -31,7 +32,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids + * @param {validBidRequests} validBidRequests an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 6074d1a0e4c..7ae1ccf9217 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -4,6 +4,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse */ @@ -24,7 +26,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest request by bidder * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index debe956d7eb..37c99878d68 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -153,7 +153,7 @@ export function getDomLoadingDuration() { } /** - * @param bidRequest + * @param bidResponse * @return {({cpm, netRevenue: boolean, requestId, width: number, currency, ttl: number, creativeId, height: number}&{mediaType: string, vastXml})|null} */ export const buildBid = (bidResponse) => { @@ -285,7 +285,6 @@ export const buildRequests = (validBidRequests, bidderRequest) => { * @description Parse the response (from buildRequests) and generate one or more bid objects. * * @param serverResponse - * @param request * @return */ const interpretResponse = (serverResponse) => { diff --git a/modules/blueconicRtdProvider.js b/modules/blueconicRtdProvider.js index 6b10e79b94c..c09fc6ee34c 100644 --- a/modules/blueconicRtdProvider.js +++ b/modules/blueconicRtdProvider.js @@ -37,9 +37,8 @@ function parseJson(data) { /** * Add real-time data & merge segments. - * @param {Object} bidConfig + * @param {Object} ortb2 * @param {Object} rtd - * @param {Object} rtdConfig */ export function addRealTimeData(ortb2, rtd) { if (isPlainObject(rtd.ortb2)) { @@ -82,7 +81,7 @@ export function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent /** * Module init * @param {Object} provider - * @param {Objkect} userConsent + * @param {Object} userConsent * @return {boolean} */ function init(provider, userConsent) { diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index 17336baa76c..2d9dcdfdf48 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -78,6 +78,7 @@ function checkConsent (userConsent) { /** * Add event- listeners to hook in to brandmetrics events * @param {Object} reqBidsConfigObj + * @param {Object} moduleConfig * @param {function} callback */ function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) { @@ -114,6 +115,7 @@ function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) { /** * Sets bid targeting of specific bidders * @param {Object} reqBidsConfigObj + * @param {Object} moduleConfig * @param {string} key Targeting key * @param {string} val Targeting value */ diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js index 37ace544dc7..dcc365faaac 100644 --- a/modules/britepoolIdSystem.js +++ b/modules/britepoolIdSystem.js @@ -38,7 +38,7 @@ export const britepoolIdSubmodule = { * @function * @param {SubmoduleConfig} [submoduleConfig] * @param {ConsentData|undefined} consentData - * @returns {function(callback:function)} + * @returns {function} */ getId(submoduleConfig, consentData) { const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 5281274616a..ab3db2a5d20 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -191,7 +191,6 @@ function getAllSlots() { /** * get prediction and return valid object for key value set * @param {number} p - * @param {string?} keyName * @return {Object} key:value */ function getKVObject(p) { diff --git a/modules/buzzoolaBidAdapter.js b/modules/buzzoolaBidAdapter.js index 14a26203484..ae77ee159bc 100644 --- a/modules/buzzoolaBidAdapter.js +++ b/modules/buzzoolaBidAdapter.js @@ -53,7 +53,6 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. - * @param bidderRequest * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function ({body}, {data}) { diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index a289e29bd19..a4accee3ce0 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -5,6 +5,7 @@ import {BANNER} from '../src/mediaTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync @@ -32,7 +33,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} validBidRequests - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index cc3e452f20c..5fe78ff932d 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -140,7 +140,7 @@ export const spec = { groupId: bid.params.group_id, bidId: bid.bidId, tid: bid.ortb2Imp?.ext?.tid, - eids: [], + eids: bid.userIdAsEids || [], floor: {} }; diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 7524cd4e194..0b840db6c26 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -98,6 +98,7 @@ export const spec = { ortb2: bidderRequest.ortb2, gdprConsent: bidderRequest.gdprConsent, uspConsent: bidderRequest.uspConsent, + gppConsent: bidderRequest.gppConsent, refererInfo: bidderRequest.refererInfo, bidRequests, }; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 0618a076193..c1fcac4ae2f 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -24,9 +24,6 @@ export const ADAPTER_VERSION = 36; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; -const FLEDGE_SELLER_DOMAIN = 'https://grid-mercury.criteo.com'; -const FLEDGE_SELLER_TIMEOUT = 500; -const FLEDGE_DECISION_LOGIC_URL = 'https://grid-mercury.criteo.com/fledge/decision'; export const PROFILE_ID_PUBLISHERTAG = 185; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; @@ -284,53 +281,13 @@ export const spec = { }); } - if (isArray(body.ext?.igbid)) { - const seller = body.ext.seller || FLEDGE_SELLER_DOMAIN; - const sellerTimeout = body.ext.sellerTimeout || FLEDGE_SELLER_TIMEOUT; - body.ext.igbid.forEach((igbid) => { - const perBuyerSignals = {}; - igbid.igbuyer.forEach(buyerItem => { - perBuyerSignals[buyerItem.origin] = buyerItem.buyerdata; - }); - const bidRequest = request.bidRequests.find(b => b.bidId === igbid.impid); - const bidId = bidRequest.bidId; - let sellerSignals = body.ext.sellerSignals || {}; - if (!sellerSignals.floor && bidRequest.params.bidFloor) { - sellerSignals.floor = bidRequest.params.bidFloor; - } - let perBuyerTimeout = { '*': 50 }; - if (sellerSignals.perBuyerTimeout) { - for (const buyer in sellerSignals.perBuyerTimeout) { - perBuyerTimeout[buyer] = sellerSignals.perBuyerTimeout[buyer]; - } - } - let perBuyerGroupLimits = { '*': 60 }; - if (sellerSignals.perBuyerGroupLimits) { - for (const buyer in sellerSignals.perBuyerGroupLimits) { - perBuyerGroupLimits[buyer] = sellerSignals.perBuyerGroupLimits[buyer]; - } - } - if (body?.ext?.sellerSignalsPerImp !== undefined) { - const sellerSignalsPerImp = body.ext.sellerSignalsPerImp[bidId]; - if (sellerSignalsPerImp !== undefined) { - sellerSignals = {...sellerSignals, ...sellerSignalsPerImp}; - } + if (isArray(body.ext?.igi)) { + body.ext.igi.forEach((igi) => { + if (isArray(igi?.igs)) { + igi.igs.forEach((igs) => { + fledgeAuctionConfigs.push(igs); + }); } - fledgeAuctionConfigs.push({ - bidId, - config: { - seller, - sellerSignals, - sellerTimeout, - perBuyerSignals, - perBuyerTimeout, - perBuyerGroupLimits, - auctionSignals: {}, - decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, - interestGroupBuyers: Object.keys(perBuyerSignals), - sellerCurrency: sellerSignals.currency || '???', - }, - }); }); } diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index d36948d162d..f878be5f66a 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -16,6 +16,7 @@ const CWID_KEY = 'cw_cwid'; export const BID_ENDPOINT = 'https://prebid.cwi.re/v1/bid'; export const EVENT_ENDPOINT = 'https://prebid.cwi.re/v1/event'; +export const GVL_ID = 1081; /** * Allows limiting ad impressions per site render. Unique per prebid instance ID. @@ -140,6 +141,7 @@ function getCwExtension() { export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: [BANNER], /** diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 775f8fc3da2..3afaacaeb81 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -54,8 +54,9 @@ Object.assign(BidInterceptor.prototype, { return { no: ruleNo, match: this.matcher(ruleDef.when, ruleNo), - replace: this.replacer(ruleDef.then || {}, ruleNo), + replace: this.replacer(ruleDef.then, ruleNo), options: Object.assign({}, this.DEFAULT_RULE_OPTIONS, ruleDef.options), + paapi: this.paapiReplacer(ruleDef.paapi || [], ruleNo) } }, /** @@ -114,6 +115,10 @@ Object.assign(BidInterceptor.prototype, { * @return {ReplacerFn} */ replacer(replDef, ruleNo) { + if (replDef === null) { + return () => null + } + replDef = replDef || {}; let replFn; if (typeof replDef === 'function') { replFn = ({args}) => replDef(...args); @@ -145,6 +150,17 @@ Object.assign(BidInterceptor.prototype, { return response; } }, + + paapiReplacer(paapiDef, ruleNo) { + if (Array.isArray(paapiDef)) { + return () => paapiDef; + } else if (typeof paapiDef === 'function') { + return paapiDef + } else { + this.logger.logError(`Invalid 'paapi' definition for debug bid interceptor (in rule #${ruleNo})`); + } + }, + responseDefaults(bid) { return { requestId: bid.bidId, @@ -198,11 +214,12 @@ Object.assign(BidInterceptor.prototype, { * @param {{}[]} bids? * @param {BidRequest} bidRequest * @param {function(*)} addBid called once for each mock response + * @param addPaapiConfig called once for each mock PAAPI config * @param {function()} done called once after all mock responses have been run through `addBid` * @returns {{bids: {}[], bidRequest: {}} remaining bids that did not match any rule (this applies also to * bidRequest.bids) */ - intercept({bids, bidRequest, addBid, done}) { + intercept({bids, bidRequest, addBid, addPaapiConfig, done}) { if (bids == null) { bids = bidRequest.bids; } @@ -211,10 +228,12 @@ Object.assign(BidInterceptor.prototype, { const callDone = delayExecution(done, matches.length); matches.forEach((match) => { const mockResponse = match.rule.replace(match.bid, bidRequest); + const mockPaapi = match.rule.paapi(match.bid, bidRequest); const delay = match.rule.options.delay; - this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) + this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response, PAAPI configs:`, match.bid, mockResponse, mockPaapi) this.setTimeout(() => { - addBid(mockResponse, match.bid); + mockResponse && addBid(mockResponse, match.bid); + mockPaapi.forEach(cfg => addPaapiConfig(cfg, match.bid, bidRequest)); callDone(); }, delay) }); diff --git a/modules/debugging/debugging.js b/modules/debugging/debugging.js index 8a4ad7a9545..2fd1731dc4e 100644 --- a/modules/debugging/debugging.js +++ b/modules/debugging/debugging.js @@ -99,7 +99,13 @@ function registerBidInterceptor(getHookFn, interceptor) { export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { const done = delayExecution(cbs.onCompletion, 2); - ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); + ({bids, bidRequest} = interceptBids({ + bids, + bidRequest, + addBid: cbs.onBid, + addPaapiConfig: (config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, config}), + done + })); if (bids.length === 0) { done(); } else { diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index 1ca13eb4927..73df01bf205 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -5,7 +5,8 @@ export function makePbsInterceptor({createBid}) { return function pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, { onResponse, onError, - onBid + onBid, + onFledge, }) { let responseArgs; const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) @@ -20,7 +21,19 @@ export function makePbsInterceptor({createBid}) { }) } bidRequests = bidRequests - .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) + .map((req) => interceptBids({ + bidRequest: req, + addBid, + addPaapiConfig(config, bidRequest, bidderRequest) { + onFledge({ + adUnitCode: bidRequest.adUnitCode, + ortb2: bidderRequest.ortb2, + ortb2Imp: bidRequest.ortb2Imp, + config + }) + }, + done + }).bidRequest) .filter((req) => req.bids.length > 0) if (bidRequests.length > 0) { diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index ad8f5616d44..de2fd3c3a94 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -65,7 +65,7 @@ const NATIVERET = { }; /** - * get page title + * get page title111 * @returns {string} */ @@ -133,10 +133,11 @@ export const getPmgUID = () => { let pmgUid = storage.getCookie(COOKIE_KEY_PMGUID); if (!pmgUid) { pmgUid = utils.generateUUID(); - try { - storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); - } catch (e) {} } + // Extend the expiration time of pmguid + try { + storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); + } catch (e) {} return pmgUid; }; @@ -436,6 +437,7 @@ function getParam(validBidRequests, bidderRequest) { const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const firstPartyData = bidderRequest.ortb2; + const tpData = utils.deepAccess(bidderRequest, 'ortb2.user.data') || undefined; const topWindow = window.top; const title = getPageTitle(); const desc = getPageDescription(); @@ -462,6 +464,7 @@ function getParam(validBidRequests, bidderRequest) { firstPartyData, ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, pmguid: getPmgUID(), + tpData, page: { title: title ? title.slice(0, 100) : undefined, desc: desc ? desc.slice(0, 300) : undefined, diff --git a/modules/dsp_genieeBidAdapter.js b/modules/dsp_genieeBidAdapter.js index 17865e6793a..57aafd47fc8 100644 --- a/modules/dsp_genieeBidAdapter.js +++ b/modules/dsp_genieeBidAdapter.js @@ -3,6 +3,16 @@ import { BANNER } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { deepAccess, deepSetValue } from '../src/utils.js'; import { config } from '../src/config.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'dsp_geniee'; const ENDPOINT_URL = 'https://rt.gsspat.jp/prebid_auction'; const ENDPOINT_URL_UNCOMFORTABLE = 'https://rt.gsspat.jp/prebid_uncomfortable'; @@ -44,7 +54,7 @@ export const spec = { /** * Determines whether or not the given bid request is valid. * - * @param {BidRequest} - The bid params to validate. + * @param {BidRequest} _ The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (_) { @@ -53,8 +63,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids - * @param {bidderRequest} - the master bidRequest object + * @param {validBidRequests} validBidRequests - an array of bids + * @param {BidderRequest} bidderRequest - the master bidRequest object * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index b4490095894..ea47c64094d 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -331,8 +331,8 @@ function getBannerSizes(bid) { /** * Parse size - * @param sizes - * @returns {width: number, h: height} + * @param size + * @returns {object} sizeObj */ function parseSize(size) { let sizeObj = {} diff --git a/modules/dxkultureBidAdapter.js b/modules/dxkultureBidAdapter.js index c167baef6ea..d803c476b6d 100644 --- a/modules/dxkultureBidAdapter.js +++ b/modules/dxkultureBidAdapter.js @@ -96,7 +96,7 @@ export const spec = { /** * Determines whether or not the given bid request is valid. * - * @param {BidRequest} bidRequest The bid params to validate. + * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { @@ -269,7 +269,7 @@ function _validateParams(bidRequest) { /** * Validates banner bid request. If it is not banner media type returns true. - * @param {object} bid, bid to validate + * @param {BidRequest} bidRequest bid to validate * @return boolean, true if valid, otherwise false */ function _validateBanner(bidRequest) { @@ -287,7 +287,7 @@ function _validateBanner(bidRequest) { /** * Validates video bid request. If it is not video media type returns true. - * @param {object} bid, bid to validate + * @param {BidRequest} bidRequest, bid to validate * @return boolean, true if valid, otherwise false */ function _validateVideo(bidRequest) { diff --git a/modules/dynamicAdBoostRtdProvider.js b/modules/dynamicAdBoostRtdProvider.js index fe08795f313..697bd7340d3 100644 --- a/modules/dynamicAdBoostRtdProvider.js +++ b/modules/dynamicAdBoostRtdProvider.js @@ -9,6 +9,10 @@ import { loadExternalScript } from '../src/adloader.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { deepAccess, deepSetValue, isEmptyStr } from '../src/utils.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const MODULE_NAME = 'dynamicAdBoost'; const SCRIPT_URL = 'https://adxbid.info'; const CLIENT_SUPPORTS_IO = window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index e68a932b726..5ee9906b5df 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -9,7 +9,7 @@ import {ajax} from '../src/ajax.js'; * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec - * @typedef {import('../src/adapters/bidderFactory.js').MediaTypes} MediaTypes + * @typedef {import('../src/adapters/bidderFactory.js').MediaType} MediaType */ /** diff --git a/modules/flippBidAdapter.js b/modules/flippBidAdapter.js index 0708c90ac0d..f9c424d9da5 100644 --- a/modules/flippBidAdapter.js +++ b/modules/flippBidAdapter.js @@ -6,6 +6,8 @@ import {getStorageManager} from '../src/storageManager.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync @@ -105,7 +107,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {BidRequest[]} validBidRequests[] an array of bids + * @param {validBidRequests} validBidRequests an array of bids * @param {BidderRequest} bidderRequest master bidRequest object * @return ServerRequest Info describing the request to the server. */ diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index f6d97fa7cd8..c0ae55efc89 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -4,6 +4,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests */ @@ -30,8 +31,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids. - * @param {bidderRequest} bidderRequest bidder request object. + * @param {validBidRequests} validBidRequests an array of bids. + * @param {BidderRequest} bidderRequest bidder request object. * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index c4653181fd0..e11aa3f8fb7 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -482,7 +482,7 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. - * @param {object} request: the built request object containing the initial bidRequest. + * @param {object} request the built request object containing the initial bidRequest. * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, request) { diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index a2ed71a898c..0b0d9027c03 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -56,7 +56,7 @@ let preloaded; /** * fetches the creative wrapper - * @param {function} sucess - success callback + * @param {function} success - success callback */ export function fetchWrapper(success) { if (wrapperReady) { diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 7353a1c1f67..a8888893333 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -111,8 +111,8 @@ function buildUrl(bid) { /** * Builds GI bid request from BidRequest. * - * @param {BidRequest} bidRequest. - * @return {object} GI bid request. + * @param {BidRequest} bidRequest + * @return {object} GI bid request */ function buildGiBidRequest(bidRequest) { let giBidRequest = { diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js index 9259010ac78..ef19a097062 100644 --- a/modules/gjirafaBidAdapter.js +++ b/modules/gjirafaBidAdapter.js @@ -5,6 +5,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests */ @@ -33,7 +34,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index c4b8cd819e0..d7af51f7312 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -15,6 +15,7 @@ import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync @@ -41,7 +42,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js index 4718438b9bb..1bcc774e351 100644 --- a/modules/gnetBidAdapter.js +++ b/modules/gnetBidAdapter.js @@ -7,6 +7,7 @@ import {ajax} from '../src/ajax.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests */ @@ -31,7 +32,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index cf72e2e5133..be20ab89130 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -9,6 +9,12 @@ import { submodule } from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'growthCodeId'; const GCID_KEY = 'gcid'; diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 01b0283d3d1..66cb5624a38 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -25,7 +25,7 @@ const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; -export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'hadron'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** * Param or default. diff --git a/modules/hypelabBidAdapter.js b/modules/hypelabBidAdapter.js index a625c7299a6..14a4758bd27 100644 --- a/modules/hypelabBidAdapter.js +++ b/modules/hypelabBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { generateUUID } from '../src/utils.js'; +import { generateUUID, isFn, isPlainObject } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; export const BIDDER_CODE = 'hypelab'; @@ -12,7 +12,7 @@ export const REPORTING_ROUTE = ''; const PREBID_VERSION = '$prebid.version$'; const PROVIDER_NAME = 'prebid'; -const PROVIDER_VERSION = '0.0.1'; +const PROVIDER_VERSION = '0.0.2'; const url = (route) => ENDPOINT_URL + route; @@ -38,18 +38,24 @@ function buildRequests(validBidRequests, bidderRequest) { const uuid = uids[0] ? uids[0] : generateTemporaryUUID(); + const floor = getBidFloor(request, request.sizes || []); + + const dpr = typeof window != 'undefined' ? window.devicePixelRatio : 1; + const payload = { property_slug: request.params.property_slug, placement_slug: request.params.placement_slug, provider_version: PROVIDER_VERSION, provider_name: PROVIDER_NAME, - referrer: + location: bidderRequest.refererInfo?.page || typeof window != 'undefined' ? window.location.href : '', sdk_version: PREBID_VERSION, sizes: request.sizes, wids: [], + floor, + dpr, uuid, bidRequestsCount: request.bidRequestsCount, bidderRequestsCount: request.bidderRequestsCount, @@ -79,6 +85,26 @@ function generateTemporaryUUID() { return 'tmp_' + generateUUID(); } +function getBidFloor(bid, sizes) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor; + + let floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: sizes.length === 1 ? sizes[0] : '*' + }); + + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + + return floor; +} + function interpretResponse(serverResponse, bidRequest) { const { data } = serverResponse.body; @@ -94,12 +120,12 @@ function interpretResponse(serverResponse, bidRequest) { creativeId: data.creative_set_slug, currency: data.currency, netRevenue: true, - referrer: bidRequest.data.referrer, + referrer: bidRequest.data.location, ttl: data.ttl, ad: data.html, mediaType: serverResponse.body.data.media_type, meta: { - advertiserDomains: data.advertiserDomains || [], + advertiserDomains: data.advertiser_domains || [], }, }; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index c585f2fdae0..42f0044edc3 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -10,6 +10,7 @@ import { deepSetValue, isEmpty, isEmptyStr, + isPlainObject, logError, logInfo, logWarn, @@ -21,8 +22,8 @@ import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -import { GreedyPromise } from '../src/utils/promise.js'; -import { loadExternalScript } from '../src/adloader.js'; +import {GreedyPromise} from '../src/utils/promise.js'; +import {loadExternalScript} from '../src/adloader.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -39,6 +40,7 @@ export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; const LOCAL_STORAGE = 'html5'; const LOG_PREFIX = 'User ID - ID5 submodule: '; const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; +const ID5_DOMAIN = 'id5-sync.com'; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use @@ -146,9 +148,17 @@ export const id5IdSubmodule = { id5id: { uid: universalUid, ext: ext - } + }, }; + if (isPlainObject(ext.euid)) { + responseObj.euid = { + uid: ext.euid.uids[0].id, + source: ext.euid.source, + ext: {provider: ID5_DOMAIN} + } + } + const abTestingResult = deepAccess(value, 'ab_testing.result'); switch (abTestingResult) { case 'control': @@ -232,7 +242,7 @@ export const id5IdSubmodule = { getValue: function(data) { return data.uid }, - source: 'id5-sync.com', + source: ID5_DOMAIN, atype: 1, getUidExt: function(data) { if (data.ext) { @@ -240,6 +250,20 @@ export const id5IdSubmodule = { } } }, + 'euid': { + getValue: function (data) { + return data.uid; + }, + getSource: function (data) { + return data.source; + }, + atype: 3, + getUidExt: function (data) { + if (data.ext) { + return data.ext; + } + } + } }, }; diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 75268e9d168..1e07ce6b5d8 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -295,6 +295,21 @@ export const spec = { } return bidResponses; } else { return []; } + }, + + getUserSyncs: function(syncOptions, serverResponses) { + if (!serverResponses.length || !serverResponses[0].body || !serverResponses[0].body.userSyncs) { + return false; + } + let syncs = []; + serverResponses[0].body.userSyncs.forEach(function(sync) { + if (syncOptions.iframeEnabled) { + syncs.push(sync.uf ? { url: sync.uf, type: 'iframe' } : { url: sync.up, type: 'image' }); + } else if (syncOptions.pixelEnabled && sync.up) { + syncs.push({url: sync.up, type: 'image'}) + } + }) + return syncs; } } diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 573ff391dae..e8c1c445816 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -192,18 +192,22 @@ export function extractPublisherParams(adUnit, fallback) { } function loadVat(params, onCompletion) { - const { playerID, mediaID } = params; + let { playerID, playerDivId, mediaID } = params; + if (!playerDivId) { + playerDivId = playerID; + } + if (pendingRequests[mediaID] !== undefined) { - loadVatForPendingRequest(playerID, mediaID, onCompletion); + loadVatForPendingRequest(playerDivId, mediaID, onCompletion); return; } - const vat = getVatFromCache(mediaID) || getVatFromPlayer(playerID, mediaID) || { mediaID }; + const vat = getVatFromCache(mediaID) || getVatFromPlayer(playerDivId, mediaID) || { mediaID }; onCompletion(vat); } -function loadVatForPendingRequest(playerID, mediaID, callback) { - const vat = getVatFromPlayer(playerID, mediaID); +function loadVatForPendingRequest(playerDivId, mediaID, callback) { + const vat = getVatFromPlayer(playerDivId, mediaID); if (vat) { callback(vat); } else { @@ -225,8 +229,8 @@ export function getVatFromCache(mediaID) { }; } -export function getVatFromPlayer(playerID, mediaID) { - const player = getPlayer(playerID); +export function getVatFromPlayer(playerDivId, mediaID) { + const player = getPlayer(playerDivId); if (!player) { return null; } @@ -362,14 +366,14 @@ export function addTargetingToBid(bid, targeting) { bid.rtd = Object.assign({}, rtd, jwRtd); } -function getPlayer(playerID) { +function getPlayer(playerDivId) { const jwplayer = window.jwplayer; if (!jwplayer) { logError(SUBMODULE_NAME + '.js was not found on page'); return; } - const player = jwplayer(playerID); + const player = jwplayer(playerDivId); if (!player || !player.getPlaylist) { logError('player ID did not match any players'); return; diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 479829196ed..a4c2f3621e1 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -36,7 +36,7 @@ const adUnit = { data: { jwTargeting: { // Note: the following Ids are placeholders and should be replaced with your Ids. - playerID: 'abcd', + playerDivId: 'abcd', mediaID: '1234' } } @@ -51,7 +51,7 @@ pbjs.que.push(function() { }); }); ``` -**Note**: The player ID is the ID of the HTML div element used when instantiating the player. +**Note**: The player Div ID is the ID of the HTML div element used when instantiating the player. You can retrieve this ID by calling `player.id`, where player is the JW Player instance variable. **Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 9d8c7bc06a1..e86d7022987 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,4 +1,4 @@ -import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; +import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -95,13 +95,16 @@ function buildRequests(validBidRequests, bidderRequest) { ] }, imp: impressions, - user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent) }); - if (firstBidRequest.ortb2 != null) { - krakenParams.site = { - cat: firstBidRequest.ortb2.site.cat + // Add full ortb2 object as backup + if (firstBidRequest.ortb2) { + const siteCat = firstBidRequest.ortb2.site?.cat; + if (siteCat != null) { + krakenParams.site = { cat: siteCat }; } + krakenParams.ext = { ortb2: firstBidRequest.ortb2 }; } // Add schain @@ -221,7 +224,7 @@ function interpretResponse(response, bidRequest) { width: adUnit.width, height: adUnit.height, ttl: 300, - creativeId: adUnit.id, + creativeId: adUnit.creativeID, dealId: adUnit.targetingCustom, netRevenue: true, currency: adUnit.currency || bidRequest.currency, @@ -459,10 +462,6 @@ function getImpression(bid) { code: bid.adUnitCode }; - if (bid.floorData != null && bid.floorData.floorMin > 0) { - imp.floor = bid.floorData.floorMin; - } - if (bid.bidRequestsCount > 0) { imp.bidRequestCount = bid.bidRequestsCount; } @@ -482,17 +481,38 @@ function getImpression(bid) { } } - if (bid.mediaTypes != null) { - if (bid.mediaTypes.banner != null) { - imp.banner = bid.mediaTypes.banner; + // Add full ortb2Imp object as backup + if (bid.ortb2Imp) { + imp.ext = { ortb2Imp: bid.ortb2Imp }; + } + + if (bid.mediaTypes) { + const { banner, video, native } = bid.mediaTypes; + + if (banner) { + imp.banner = banner; + } + + if (video) { + imp.video = video; } - if (bid.mediaTypes.video != null) { - imp.video = bid.mediaTypes.video; + if (native) { + imp.native = native; } - if (bid.mediaTypes.native != null) { - imp.native = bid.mediaTypes.native; + if (typeof bid.getFloor === 'function') { + let floorInfo; + try { + floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + } catch (e) { + logError('Kargo: getFloor threw an error: ', e); + } + imp.floor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined; } } diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 9a336b16136..264592cd7d6 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -173,7 +173,7 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); + const bidderTimeout = bidderRequest.timeout ?? config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 4e0a62cca0a..786feeb8052 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -11,6 +11,7 @@ import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/val import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {UID1_EIDS} from '../libraries/uid1Eids/uid1Eids.js'; import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -236,7 +237,9 @@ export const liveIntentIdSubmodule = { } if (value.thetradedesk) { - result.thetradedesk = { 'id': value.thetradedesk, ext: { provider: getRefererInfo().domain || LI_PROVIDER_DOMAIN } } + result.lipb = {...result.lipb, tdid: value.thetradedesk} + result.tdid = { 'id': value.thetradedesk, ext: { rtiPartner: 'TDID', provider: getRefererInfo().domain || LI_PROVIDER_DOMAIN } } + delete result.lipb.thetradedesk } return result @@ -278,6 +281,7 @@ export const liveIntentIdSubmodule = { return { callback: result }; }, eids: { + ...UID1_EIDS, ...UID2_EIDS, 'lipb': { getValue: function(data) { @@ -376,18 +380,6 @@ export const liveIntentIdSubmodule = { return data.ext; } } - }, - 'thetradedesk': { - source: 'adserver.org', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } } } }; diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js index 8958e8f3786..ab7f96c4e60 100644 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -1,36 +1,41 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {loadExternalScript} from '../src/adloader.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getUniqueIdentifierStr, logInfo} from '../src/utils.js'; +import {getUniqueIdentifierStr, logInfo, deepSetValue} from '../src/utils.js'; import {fetch} from '../src/ajax.js'; const bidderCode = 'lucead'; -let baseUrl = 'https://ayads.io'; -let staticUrl = 'https://s.ayads.io'; +const bidderName = 'Lucead'; +let baseUrl = 'https://lucead.com'; +let staticUrl = 'https://s.lucead.com'; let companionUrl = 'https://cdn.jsdelivr.net/gh/lucead/prebid-js-external-js-lucead@master/dist/prod.min.js'; -let endpointUrl = 'https://prebid.ayads.io/go'; +let endpointUrl = 'https://prebid.lucead.com/go'; const defaultCurrency = 'EUR'; const defaultTtl = 500; -const isDevEnv = location.hostname.endsWith('.ngrok-free.app'); +const aliases = ['adliveplus']; + +function isDevEnv() { + return location.hash.includes('prebid-dev') || location.href.startsWith('https://ayads.io/test'); +} function isBidRequestValid(bidRequest) { return !!bidRequest?.params?.placementId; } export function log(msg, obj) { - logInfo('Lucead - ' + msg, obj); + logInfo(`${bidderName} - ${msg}`, obj); } -function buildRequests(validBidRequests, bidderRequest) { - if (isDevEnv) { - baseUrl = `https://${location.hostname}`; +function buildRequests(bidRequests, bidderRequest) { + if (isDevEnv()) { + baseUrl = location.origin; staticUrl = baseUrl; companionUrl = `${staticUrl}/dist/prebid-companion.js`; endpointUrl = `${baseUrl}/go`; } log('buildRequests', { - validBidRequests, + bidRequests, bidderRequest, }); @@ -39,15 +44,17 @@ function buildRequests(validBidRequests, bidderRequest) { static_url: staticUrl, endpoint_url: endpointUrl, request_id: bidderRequest.bidderRequestId, - validBidRequests, + prebid_version: '$prebid.version$', + bidRequests, bidderRequest, getUniqueIdentifierStr, ortbConverter, + deepSetValue, }; loadExternalScript(companionUrl, bidderCode, () => window.ayads_prebid && window.ayads_prebid(companionData)); - return validBidRequests.map(bidRequest => ({ + return bidRequests.map(bidRequest => ({ method: 'POST', url: `${endpointUrl}/prebid/sub`, data: JSON.stringify({ @@ -80,7 +87,7 @@ function interpretResponse(serverResponse, bidRequest) { height: (response?.size && response?.size?.height) || 250, currency: response?.currency || defaultCurrency, ttl: response?.ttl || defaultTtl, - creativeId: response?.ad_id || '0', + creativeId: response.ssp ? `ssp:${response.ssp}` : (response?.ad_id || '0'), netRevenue: response?.netRevenue || true, ad: response?.ad || '', meta: { @@ -119,13 +126,22 @@ function report(type = 'impression', data = {}) { function onBidWon(bid) { log('Bid won', bid); - return report(`impression`, { + let data = { bid_id: bid?.bidId, - ad_id: bid?.creativeId, placement_id: bid?.params ? bid?.params[0]?.placementId : 0, spent: bid?.cpm, currency: bid?.currency, - }); + }; + + if (bid.creativeId) { + if (bid.creativeId.toString().startsWith('ssp:')) { + data.ssp = bid.creativeId.split(':')[1]; + } else { + data.ad_id = bid.creativeId; + } + } + + return report(`impression`, data); } function onTimeout(timeoutData) { @@ -135,12 +151,13 @@ function onTimeout(timeoutData) { export const spec = { code: bidderCode, // gvlid: BIDDER_GVLID, - aliases: [], + aliases, isBidRequestValid, buildRequests, interpretResponse, onBidWon, onTimeout, + isDevEnv, }; // noinspection JSCheckFunctionSignatures diff --git a/modules/luceadBidAdapter.md b/modules/luceadBidAdapter.md index 45fd3ec5301..953c911cd2b 100644 --- a/modules/luceadBidAdapter.md +++ b/modules/luceadBidAdapter.md @@ -1,7 +1,9 @@ # Overview Module Name: Lucead Bidder Adapter + Module Type: Bidder Adapter + Maintainer: prebid@lucead.com # Description @@ -16,9 +18,9 @@ const adUnits = [ sizes: [[300, 250]], bids: [ { - bidder: "lucead", + bidder: 'lucead', params: { - placementId: '1', + placementId: '2', } } ] diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 3b70a51cd68..5cc45e3adbf 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -46,6 +46,7 @@ const pbsErrorMap = { 4: 'request-error', 999: 'generic-error' } +let cookieless; let prebidGlobal = getGlobal(); const { @@ -332,10 +333,14 @@ const getTopLevelDetails = () => { // Add DM wrapper details if (rubiConf.wrapperName) { + let rule; + if (cookieless) { + rule = rubiConf.rule_name ? rubiConf.rule_name.concat('_cookieless') : 'cookieless'; + } payload.wrapper = { name: rubiConf.wrapperName, family: rubiConf.wrapperFamily, - rule: rubiConf.rule_name + rule } } @@ -823,6 +828,15 @@ magniteAdapter.track = ({ eventType, args }) => { auctionData.floors = addFloorData(floorData); } + // Identify chrome cookieless trafic + if (!cookieless) { + const cdep = deepAccess(args, 'bidderRequests.0.ortb2.device.ext.cdep'); + if (cdep && (cdep.indexOf('treatment') !== -1 || cdep.indexOf('control_2') !== -1)) { + cookieless = 1; + auctionData.cdep = 1; + } + } + // GDPR info const gdprData = deepAccess(args, 'bidderRequests.0.gdprConsent'); if (gdprData) { diff --git a/modules/mediaimpactBidAdapter.js b/modules/mediaimpactBidAdapter.js new file mode 100644 index 00000000000..9a86632f052 --- /dev/null +++ b/modules/mediaimpactBidAdapter.js @@ -0,0 +1,211 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { buildUrl } from '../src/utils.js' +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'mediaimpact'; +export const ENDPOINT_PROTOCOL = 'https'; +export const ENDPOINT_DOMAIN = 'bidder.smartytouch.co'; +export const ENDPOINT_PATH = '/hb/bid'; + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function (bidRequest) { + return !!parseInt(bidRequest.params.unitId) || !!parseInt(bidRequest.params.partnerId); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + // TODO does it make sense to fall back to window.location.href? + const referer = bidderRequest?.refererInfo?.page || window.location.href; + + let bidRequests = []; + let beaconParams = { + tag: [], + partner: [], + sizes: [], + referer: '' + }; + + validBidRequests.forEach(function(validBidRequest) { + let bidRequestObject = { + adUnitCode: validBidRequest.adUnitCode, + sizes: validBidRequest.sizes, + bidId: validBidRequest.bidId, + referer: referer + }; + + if (parseInt(validBidRequest.params.unitId)) { + bidRequestObject.unitId = parseInt(validBidRequest.params.unitId); + beaconParams.tag.push(validBidRequest.params.unitId); + } + + if (parseInt(validBidRequest.params.partnerId)) { + bidRequestObject.unitId = 0; + bidRequestObject.partnerId = parseInt(validBidRequest.params.partnerId); + beaconParams.partner.push(validBidRequest.params.partnerId); + } + + bidRequests.push(bidRequestObject); + + beaconParams.sizes.push(spec.joinSizesToString(validBidRequest.sizes)); + beaconParams.referer = encodeURIComponent(referer); + }); + + if (beaconParams.partner.length > 0) { + beaconParams.partner = beaconParams.partner.join(','); + } else { + delete beaconParams.partner; + } + + beaconParams.tag = beaconParams.tag.join(','); + beaconParams.sizes = beaconParams.sizes.join(','); + + let adRequestUrl = buildUrl({ + protocol: ENDPOINT_PROTOCOL, + hostname: ENDPOINT_DOMAIN, + pathname: ENDPOINT_PATH, + search: beaconParams + }); + + return { + method: 'POST', + url: adRequestUrl, + data: JSON.stringify(bidRequests) + }; + }, + + joinSizesToString: function(sizes) { + let res = []; + sizes.forEach(function(size) { + res.push(size.join('x')); + }); + + return res.join('|'); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const validBids = JSON.parse(bidRequest.data); + + if (typeof serverResponse.body === 'undefined') { + return []; + } + + return validBids + .map(bid => ({ + bid: bid, + ad: serverResponse.body[bid.adUnitCode] + })) + .filter(item => item.ad) + .map(item => spec.adResponse(item.bid, item.ad)); + }, + + adResponse: function(bid, ad) { + const bidObject = { + requestId: bid.bidId, + ad: ad.ad, + cpm: ad.cpm, + width: ad.width, + height: ad.height, + ttl: 60, + creativeId: ad.creativeId, + netRevenue: ad.netRevenue, + currency: ad.currency, + winNotification: ad.winNotification + } + + bidObject.meta = {}; + if (ad.adomain && ad.adomain.length > 0) { + bidObject.meta.advertiserDomains = ad.adomain; + } + + return bidObject; + }, + + onBidWon: function(data) { + data.winNotification.forEach(function(unitWon) { + let adBidWonUrl = buildUrl({ + protocol: ENDPOINT_PROTOCOL, + hostname: ENDPOINT_DOMAIN, + pathname: unitWon.path + }); + + if (unitWon.method === 'POST') { + spec.postRequest(adBidWonUrl, JSON.stringify(unitWon.data)); + } + }); + + return true; + }, + + postRequest(endpoint, data) { + ajax(endpoint, null, data, {method: 'POST'}); + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + let appendGdprParams = function (url, gdprParams) { + if (gdprParams === null) { + return url; + } + + return url + (url.indexOf('?') >= 0 ? '&' : '?') + gdprParams; + }; + + let gdprParams = null; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + serverResponses.forEach(resp => { + if (resp.body) { + Object.keys(resp.body).map(function(key, index) { + let respObject = resp.body[key]; + if (respObject['syncs'] !== undefined && + Array.isArray(respObject.syncs) && + respObject.syncs.length > 0) { + if (syncOptions.iframeEnabled) { + respObject.syncs.filter(function (syncIframeObject) { + if (syncIframeObject['type'] !== undefined && + syncIframeObject['link'] !== undefined && + syncIframeObject.type === 'iframe') { return true; } + return false; + }).forEach(function (syncIframeObject) { + syncs.push({ + type: 'iframe', + url: appendGdprParams(syncIframeObject.link, gdprParams) + }); + }); + } + if (syncOptions.pixelEnabled) { + respObject.syncs.filter(function (syncImageObject) { + if (syncImageObject['type'] !== undefined && + syncImageObject['link'] !== undefined && + syncImageObject.type === 'image') { return true; } + return false; + }).forEach(function (syncImageObject) { + syncs.push({ + type: 'image', + url: appendGdprParams(syncImageObject.link, gdprParams) + }); + }); + } + } + }); + } + }); + + return syncs; + }, + +} + +registerBidder(spec); diff --git a/modules/mediaimpactBidAdapter.md b/modules/mediaimpactBidAdapter.md new file mode 100644 index 00000000000..2fc6697fffb --- /dev/null +++ b/modules/mediaimpactBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +Module Name: MEDIAIMPACT Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: Info@mediaimpact.com.ua + +# Description + +You can use this adapter to get a bid from mediaimpact.com.ua. + +About us : https://mediaimpact.com.ua + + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'div-ad-example', + sizes: [[300, 250]], + bids: [ + { + bidder: "mediaimpact", + params: { + unitId: 6698 + } + } + ] + }, + { + code: 'div-ad-example-2', + sizes: [[300, 250]], + bids: [ + { + bidder: "mediaimpact", + params: { + partnerId: 6698 + } + } + ] + } + ]; +``` diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index d151523b265..de91b508125 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -8,7 +8,6 @@ import { getWindowTop, isArray, isStr, - logMessage, parseGPTSingleSizeArrayToRtbSize, parseUrl, triggerPixel, @@ -18,7 +17,6 @@ import {getGlobal} from '../src/prebidGlobal.js'; import CONSTANTS from '../src/constants.json'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import * as events from '../src/events.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; @@ -33,18 +31,32 @@ const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric'; const TIME_TO_LIVE = 360; const DEFAULT_CURRENCY = 'USD'; -const VIDEO_PARAMS = [ - 'api', - 'linearity', - 'maxduration', - 'mimes', - 'minduration', - 'placement', - 'playbackmethod', - 'protocols', - 'startdelay', -]; +const VIDEO_PARAMS_DEFAULT = { + api: undefined, + context: undefined, + delivery: undefined, + linearity: undefined, + maxduration: undefined, + mimes: [ + 'video/mp4', + 'video/x-ms-wmv', + 'application/javascript', + ], + + minduration: undefined, + placement: undefined, + plcmt: undefined, + playbackend: undefined, + playbackmethod: undefined, + pos: undefined, + protocols: undefined, + skip: undefined, + skipafter: undefined, + skipmin: undefined, + startdelay: undefined, +}; +const VIDEO_PARAMS = Object.keys(VIDEO_PARAMS_DEFAULT); const ALLOWED_ORTB2_PARAMETERS = [ 'site.pagecat', 'site.content.cat', @@ -55,15 +67,6 @@ const ALLOWED_ORTB2_PARAMETERS = [ 'user.keywords', ]; -const sendingDataStatistic = initSendingDataStatistic(); -events.on(CONSTANTS.EVENTS.AUCTION_INIT, auctionInitHandler); - -const EXPIRENCE_WURL = 20 * 60000; -const wurlMap = {}; -cleanWurl(); - -events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -79,7 +82,7 @@ export const spec = { const requests = []; window.nmmRefreshCounts = window.nmmRefreshCounts || {}; - _each(validBidRequests, function(bid) { + _each(validBidRequests, (bid) => { window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; const id = getPlacementId(bid); const auctionId = bid.auctionId; @@ -135,6 +138,8 @@ export const spec = { params, auctionId, }); + + this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_REQUESTED, bid); }); return requests; @@ -148,11 +153,6 @@ export const spec = { _each(resp.bid, (bid) => { const requestId = bidRequest.bidId; const params = bidRequest.params; - const auctionId = bidRequest.auctionId; - const wurl = deepAccess(bid, 'ext.prebid.events.win'); - - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - addWurl({auctionId, requestId, wurl}); const {ad, adUrl, vastUrl, vastXml} = getAd(bid); @@ -182,6 +182,8 @@ export const spec = { }; bidResponses.push(bidResponse); + + this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_RESPONSE, bid); }); }); @@ -215,6 +217,16 @@ export const spec = { }, getUrlPixelMetric(eventName, bid) { + const disabledSending = !!config.getBidderConfig()?.nextMillennium?.disabledSendingStatisticData; + if (disabledSending) return; + + const url = this._getUrlPixelMetric(eventName, bid); + if (!url) return; + + triggerPixel(url); + }, + + _getUrlPixelMetric(eventName, bid) { const bidder = bid.bidder || bid.bidderCode; if (bidder != BIDDER_CODE) return; @@ -248,6 +260,12 @@ export const spec = { return url; }, + + onTimeout(bids) { + for (const bid of bids) { + this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_TIMEOUT, bid); + }; + }, }; export function getImp(bid, id, mediaTypes) { @@ -263,29 +281,46 @@ export function getImp(bid, id, mediaTypes) { }, }; - if (banner) { - if (banner.bidfloorcur) imp.bidfloorcur = banner.bidfloorcur; - if (banner.bidfloor) imp.bidfloor = banner.bidfloor; + getImpBanner(imp, banner); + getImpVideo(imp, video); - imp.banner = { - format: (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }), - }; - }; + return imp; +}; - if (video) { - if (video.bidfloorcur) imp.bidfloorcur = video.bidfloorcur; - if (video.bidfloor) imp.bidfloor = video.bidfloor; +export function getImpBanner(imp, banner) { + if (!banner) return; - imp.video = getDefinedParams(video, VIDEO_PARAMS); - if (video.data.playerSize) { - imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(video.data.playerSize) || {}); - } else if (video.w && video.h) { - imp.video.w = video.w; - imp.video.h = video.h; - }; + if (banner.bidfloorcur) imp.bidfloorcur = banner.bidfloorcur; + if (banner.bidfloor) imp.bidfloor = banner.bidfloor; + + const format = (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }) + const {w, h} = (format[0] || {}) + imp.banner = { + w, + h, + format, }; +}; - return imp; +export function getImpVideo(imp, video) { + if (!video) return; + + if (video.bidfloorcur) imp.bidfloorcur = video.bidfloorcur; + if (video.bidfloor) imp.bidfloor = video.bidfloor; + + imp.video = getDefinedParams(video.data, VIDEO_PARAMS); + Object.keys(VIDEO_PARAMS_DEFAULT) + .filter(videoParamName => VIDEO_PARAMS_DEFAULT[videoParamName]) + .forEach(videoParamName => { + if (typeof imp.video[videoParamName] === 'undefined') imp.video[videoParamName] = VIDEO_PARAMS_DEFAULT[videoParamName]; + }); + + if (video.data.playerSize) { + imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(video.data?.playerSize) || {}); + } else if (video.data.w && video.data.h) { + imp.video.w = video.data.w; + imp.video.h = video.data.h; + }; }; export function setConsentStrings(postBody = {}, bidderRequest) { @@ -487,126 +522,4 @@ function getSua() { }; } -function getKeyWurl({auctionId, requestId}) { - return `${auctionId}-${requestId}`; -} - -function addWurl({wurl, requestId, auctionId}) { - if (!wurl) return; - - const expirence = Date.now() + EXPIRENCE_WURL; - const key = getKeyWurl({auctionId, requestId}); - wurlMap[key] = {wurl, expirence}; -} - -function removeWurl({auctionId, requestId}) { - const key = getKeyWurl({auctionId, requestId}); - delete wurlMap[key]; -} - -function getWurl({auctionId, requestId}) { - const key = getKeyWurl({auctionId, requestId}); - return wurlMap[key] && wurlMap[key].wurl; -} - -function bidWonHandler(bid) { - const {auctionId, requestId} = bid; - const wurl = getWurl({auctionId, requestId}); - if (wurl) { - logMessage(`(nextmillennium) Invoking image pixel for wurl on BID_WIN: "${wurl}"`); - triggerPixel(wurl); - removeWurl({auctionId, requestId}); - }; -} - -function auctionInitHandler() { - sendingDataStatistic.initEvents(); -} - -function cleanWurl() { - const dateNow = Date.now(); - Object.keys(wurlMap).forEach(key => { - if (dateNow >= wurlMap[key].expirence) { - delete wurlMap[key]; - }; - }); - - setTimeout(cleanWurl, 60000); -} - -function initSendingDataStatistic() { - class SendingDataStatistic { - eventNames = [ - CONSTANTS.EVENTS.BID_TIMEOUT, - CONSTANTS.EVENTS.BID_RESPONSE, - CONSTANTS.EVENTS.BID_REQUESTED, - CONSTANTS.EVENTS.NO_BID, - ]; - - disabledSending = false; - enabledSending = false; - eventHendlers = {}; - - initEvents() { - this.disabledSending = !!config.getBidderConfig()?.nextMillennium?.disabledSendingStatisticData; - if (this.disabledSending) { - this.removeEvents(); - } else { - this.createEvents(); - }; - } - - createEvents() { - if (this.enabledSending) return; - - this.enabledSending = true; - for (let eventName of this.eventNames) { - if (!this.eventHendlers[eventName]) { - this.eventHendlers[eventName] = this.eventHandler(eventName); - }; - - events.on(eventName, this.eventHendlers[eventName]); - }; - } - - removeEvents() { - if (!this.enabledSending) return; - - this.enabledSending = false; - for (let eventName of this.eventNames) { - if (!this.eventHendlers[eventName]) continue; - - events.off(eventName, this.eventHendlers[eventName]); - }; - } - - eventHandler(eventName) { - const eventHandlerFunc = this.getEventHandler(eventName); - if (eventName == CONSTANTS.EVENTS.BID_TIMEOUT) { - return bids => { - if (this.disabledSending || !Array.isArray(bids)) return; - - for (let bid of bids) { - eventHandlerFunc(bid); - }; - } - }; - - return eventHandlerFunc; - } - - getEventHandler(eventName) { - return bid => { - if (this.disabledSending) return; - - const url = spec.getUrlPixelMetric(eventName, bid); - if (!url) return; - triggerPixel(url); - }; - } - }; - - return new SendingDataStatistic(); -} - registerBidder(spec); diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index baadaa272e6..c31c3d81aeb 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -22,7 +22,7 @@ const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstre const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '3.0'; +const BIDDER_VERSION = '4.0'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; @@ -151,7 +151,6 @@ function isBidRequestValid(bid) { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ @@ -208,7 +207,7 @@ function interpretResponse(serverResponse) { response.adUrl = bid.ext.adUrl; } } - if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType)) response.vastXml = bid.ext.vastXml; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType)) response.vastXml = bid.adm; if (bid.ext.mediaType === OUTSTREAM) { response.renderer = createRenderer(bid, OUTSTREAM_RENDERER_URL); diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index 2c119e28610..3a272c3f796 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -6,7 +6,7 @@ import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const VERSION = '2.0.0'; +const VERSION = '2.0.1'; const MODULE_NAME = 'nobidAnalyticsAdapter'; const ANALYTICS_OPT_FLUSH_TIMEOUT_SECONDS = 5 * 1000; const RETENTION_SECONDS = 1 * 24 * 3600; @@ -54,6 +54,7 @@ function sendEvent (event, eventType) { return; } try { + event.version = VERSION; const endpoint = `${resolveEndpoint()}/event/${eventType}?pubid=${nobidAnalytics.initOptions.siteId}`; ajax(endpoint, function (response) { @@ -83,7 +84,7 @@ function cleanupObjectAttributes (obj, attributes) { } function sendBidWonEvent (event, eventType) { const data = deepClone(event); - cleanupObjectAttributes(data, ['bidderCode', 'size', 'statusMessage', 'adId', 'requestId', 'mediaType', 'adUnitCode', 'cpm', 'timeToRespond']); + cleanupObjectAttributes(data, ['bidderCode', 'size', 'statusMessage', 'adId', 'requestId', 'mediaType', 'adUnitCode', 'cpm', 'currency', 'originalCpm', 'originalCurrency', 'timeToRespond']); if (nobidAnalytics.topLocation) data.topLocation = nobidAnalytics.topLocation; sendEvent(data, eventType); } @@ -95,7 +96,7 @@ function sendAuctionEndEvent (event, eventType) { cleanupObjectAttributes(data, ['timestamp', 'timeout', 'auctionId', 'bidderRequests', 'bidsReceived']); if (data) cleanupObjectAttributes(data.bidderRequests, ['bidderCode', 'bidderRequestId', 'bids', 'refererInfo']); - if (data) cleanupObjectAttributes(data.bidsReceived, ['bidderCode', 'width', 'height', 'adUnitCode', 'statusMessage', 'requestId', 'mediaType', 'cpm']); + if (data) cleanupObjectAttributes(data.bidsReceived, ['bidderCode', 'width', 'height', 'adUnitCode', 'statusMessage', 'requestId', 'mediaType', 'cpm', 'currency', 'originalCpm', 'originalCurrency']); if (data) cleanupObjectAttributes(data.noBids, ['bidder', 'sizes', 'bidId']); if (data.bidderRequests) { data.bidderRequests.forEach(bidderRequest => { diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index bef9a43749f..e6c8f8b098e 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -45,15 +45,19 @@ function buildRequests(bidReqs, bidderRequest) { const minSize = _getMinSize(processedSizes); const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + const gpidData = _extractGpidData(bid); const imp = { id: bid.bidId, banner: { format: processedSizes, ext: { - viewability: viewabilityAmountRounded + viewability: viewabilityAmountRounded, } }, + ext: { + ...gpidData + }, tagid: String(bid.adUnitCode) }; @@ -241,6 +245,15 @@ function _getViewability(element, topWin, {w, h} = {}) { return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, topWin, {w, h}) : 0; } +function _extractGpidData(bid) { + return { + gpid: bid?.ortb2Imp?.ext?.gpid, + adserverName: bid?.ortb2Imp?.ext?.data?.adserver?.name, + adslot: bid?.ortb2Imp?.ext?.data?.adserver?.adslot, + pbadslot: bid?.ortb2Imp?.ext?.data?.pbadslot, + } +} + function _isIframe() { try { return getWindowSelf() !== getWindowTop(); diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 7a7cbbadd82..d8423253aaf 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -63,7 +63,8 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidderRequest && bidderRequest.gdprConsent) { payload.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, - consentRequired: bidderRequest.gdprConsent.gdprApplies + consentRequired: bidderRequest.gdprConsent.gdprApplies, + addtlConsent: bidderRequest.gdprConsent.addtlConsent }; } if (bidderRequest && bidderRequest.gppConsent) { diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js new file mode 100644 index 00000000000..87d00f14de0 --- /dev/null +++ b/modules/opscoBidAdapter.js @@ -0,0 +1,129 @@ +import {deepAccess, deepSetValue, isArray, logInfo} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const ENDPOINT = 'https://exchange.ops.co/openrtb2/auction'; +const BIDDER_CODE = 'opsco'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => !!(bid.params && + bid.params.placementId && + bid.params.publisherId && + bid.mediaTypes?.banner?.sizes && + Array.isArray(bid.mediaTypes?.banner?.sizes)), + + buildRequests: (validBidRequests, bidderRequest) => { + const {publisherId, placementId, siteId} = validBidRequests[0].params; + + const payload = { + id: bidderRequest.bidderRequestId, + imp: validBidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: {format: extractSizes(bidRequest)}, + ext: { + opsco: { + placementId: placementId, + publisherId: publisherId, + } + } + })), + site: { + id: siteId, + publisher: {id: publisherId}, + domain: bidderRequest.refererInfo?.domain, + page: bidderRequest.refererInfo?.page, + ref: bidderRequest.refererInfo?.ref, + }, + }; + + if (isTest(validBidRequests[0])) { + payload.test = 1; + } + + if (bidderRequest.gdprConsent) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (eids && eids.length !== 0) { + deepSetValue(payload, 'user.ext.eids', eids); + } + + const schainData = deepAccess(validBidRequests[0], 'schain.nodes'); + if (isArray(schainData) && schainData.length > 0) { + deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + } + + if (bidderRequest.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload), + }; + }, + + interpretResponse: (serverResponse) => { + const response = (serverResponse || {}).body; + const bidResponses = response?.seatbid?.[0]?.bid?.map(bid => ({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + meta: {advertiserDomains: bid?.adomain || []}, + mediaType: bid.mediaType || bid.mtype + })) || []; + + if (!bidResponses.length) { + logInfo('opsco.interpretResponse :: No valid responses'); + } + + return bidResponses; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + logInfo('opsco.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return []; + } + let syncs = []; + serverResponses.forEach(resp => { + const userSync = deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + const syncDetails = Object.values(userSync).flatMap(value => value.syncs || []); + syncDetails.forEach(syncDetail => { + const type = syncDetail.type === 'iframe' ? 'iframe' : 'image'; + if ((type === 'iframe' && syncOptions.iframeEnabled) || (type === 'image' && syncOptions.pixelEnabled)) { + syncs.push({type, url: syncDetail.url}); + } + }); + } + }); + + logInfo('opsco.getUserSyncs result=%o', syncs); + return syncs; + } +}; + +function extractSizes(bidRequest) { + return (bidRequest.mediaTypes?.banner?.sizes || []).map(([width, height]) => ({w: width, h: height})); +} + +function isTest(validBidRequest) { + return validBidRequest.params?.test === true; +} + +registerBidder(spec); diff --git a/modules/opscoBidAdapter.md b/modules/opscoBidAdapter.md new file mode 100644 index 00000000000..b5e1015a325 --- /dev/null +++ b/modules/opscoBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Opsco Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@ops.co +``` + +# Description + +Module that connects to Opscos's demand sources. + +# Test Parameters + +## Banner + +``` +var adUnits = [ + { + code: 'test-ad', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'opsco', + params: { + placementId: '1234', + publisherId: '9876', + test: true + } + }], + } +]; +``` diff --git a/modules/pangleBidAdapter.js b/modules/pangleBidAdapter.js index f4a52168743..c22a44687a2 100644 --- a/modules/pangleBidAdapter.js +++ b/modules/pangleBidAdapter.js @@ -1,4 +1,3 @@ -// ver V1.0.4 import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -155,15 +154,16 @@ export const spec = { }, buildRequests(bidRequests, bidderRequest) { + const reqArr = []; const videoBids = bidRequests.filter((bid) => isVideoBid(bid)); const bannerBids = bidRequests.filter((bid) => isBannerBid(bid)); - let requests = bannerBids.length - ? [createRequest(bannerBids, bidderRequest, BANNER)] - : []; + bannerBids.forEach((bid) => { + reqArr.push(createRequest([bid], bidderRequest, BANNER)); + }) videoBids.forEach((bid) => { - requests.push(createRequest([bid], bidderRequest, VIDEO)); + reqArr.push(createRequest([bid], bidderRequest, VIDEO)); }); - return requests; + return reqArr; }, interpretResponse(response, request) { diff --git a/modules/pilotxBidAdapter.js b/modules/pilotxBidAdapter.js index 0fb39e19076..417c1f0c089 100644 --- a/modules/pilotxBidAdapter.js +++ b/modules/pilotxBidAdapter.js @@ -45,7 +45,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { @@ -143,7 +143,7 @@ export const spec = { /** * Formats placement ids for adserver ingestion purposes - * @param {string[]} The placement ID/s in an array + * @param {string[]} placementId the placement ID/s in an array */ setPlacementID: function (placementId) { if (Array.isArray(placementId)) { diff --git a/modules/prismaBidAdapter.js b/modules/prismaBidAdapter.js index 4b4b5677cb3..b42c4b8af3f 100644 --- a/modules/prismaBidAdapter.js +++ b/modules/prismaBidAdapter.js @@ -64,7 +64,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { @@ -189,7 +189,7 @@ export const spec = { /** * Register bidder specific code, which will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction + * @param {Bid} bid the bid that won the auction */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid diff --git a/modules/pstudioBidAdapter.js b/modules/pstudioBidAdapter.js new file mode 100644 index 00000000000..77a11ac58c6 --- /dev/null +++ b/modules/pstudioBidAdapter.js @@ -0,0 +1,435 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + deepAccess, + isArray, + isNumber, + generateUUID, + isEmpty, + isFn, + isPlainObject, +} from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const BIDDER_CODE = 'pstudio'; +const ENDPOINT = 'https://exchange.pstudio.tadex.id/prebid-bid' +const TIME_TO_LIVE = 300; +// in case that the publisher limits number of user syncs, thisse syncs will be discarded from the end of the list +// so more improtant syncing calls should be at the start of the list +const USER_SYNCS = [ + // PARTNER_UID is a partner user id + { + type: 'img', + url: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=k1on5ig&ttd_tpi=1&ttd_puid=%PARTNER_UID%&dsp=ttd', + macro: '%PARTNER_UID%', + }, + { + type: 'img', + url: 'https://dsp.myads.telkomsel.com/api/v1/pixel?uid=%USERID%', + macro: '%USERID%', + }, +]; +const COOKIE_NAME = '__tadexid'; +const COOKIE_TTL_DAYS = 365; +const DAY_IN_MS = 24 * 60 * 60 * 1000; +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const VIDEO_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'protocols', + 'startdelay', + 'placement', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity', +]; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + isBidRequestValid: function (bid) { + const params = bid.params || {}; + return !!params.pubid && !!params.floorPrice && isVideoRequestValid(bid); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map((bid) => ({ + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(buildRequestData(bid, bidderRequest)), + options: { + contentType: 'application/json', + withCredentials: true, + }, + })); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + + if (!serverResponse.body.bids) return []; + const { id } = JSON.parse(bidRequest.data); + + serverResponse.body.bids.map((bid) => { + const { cpm, width, height, currency, ad, meta } = bid; + let bidResponse = { + requestId: id, + cpm, + width, + height, + creativeId: bid.creative_id, + currency, + netRevenue: bid.net_revenue, + ttl: TIME_TO_LIVE, + meta: { + advertiserDomains: meta.advertiser_domains, + }, + }; + + if (bid.vast_url || bid.vast_xml) { + bidResponse.vastUrl = bid.vast_url; + bidResponse.vastXml = bid.vast_xml; + bidResponse.mediaType = VIDEO; + } else { + bidResponse.ad = ad; + } + + bidResponses.push(bidResponse); + }); + + return bidResponses; + }, + + getUserSyncs(_optionsType, _serverResponse, _gdprConsent, _uspConsent) { + const syncs = []; + + let userId = readUserIdFromCookie(COOKIE_NAME); + + if (!userId) { + userId = generateId(); + writeIdToCookie(COOKIE_NAME, userId); + } + + USER_SYNCS.map((userSync) => { + if (userSync.type === 'img') { + syncs.push({ + type: 'image', + url: userSync.url.replace(userSync.macro, userId), + }); + } + }); + + return syncs; + }, +}; + +function buildRequestData(bid, bidderRequest) { + let payloadObject = buildBaseObject(bid, bidderRequest); + + if (bid.mediaTypes.banner) { + return buildBannerObject(bid, payloadObject); + } else if (bid.mediaTypes.video) { + return buildVideoObject(bid, payloadObject); + } +} + +function buildBaseObject(bid, bidderRequest) { + const firstPartyData = prepareFirstPartyData(bidderRequest.ortb2); + const { pubid, bcat, badv, bapp } = bid.params; + const { userId } = bid; + const uid2Token = userId?.uid2?.id; + + if (uid2Token) { + if (firstPartyData.user) { + firstPartyData.user.uid2_token = uid2Token; + } else { + firstPartyData.user = { uid2_token: uid2Token }; + } + } + const userCookieId = readUserIdFromCookie(COOKIE_NAME); + if (userCookieId) { + if (firstPartyData.user) { + firstPartyData.user.id = userCookieId; + } else { + firstPartyData.user = { id: userCookieId }; + } + } + + return { + id: bid.bidId, + pubid, + floor_price: getBidFloor(bid), + adtagid: bid.adUnitCode, + ...(bcat && { bcat }), + ...(badv && { badv }), + ...(bapp && { bapp }), + ...firstPartyData, + }; +} + +function buildBannerObject(bid, payloadObject) { + const { sizes, pos, name } = bid.mediaTypes.banner; + + payloadObject.banner_properties = { + name, + sizes, + pos, + }; + + return payloadObject; +} + +function buildVideoObject(bid, payloadObject) { + const { context, playerSize, w, h } = bid.mediaTypes.video; + + payloadObject.video_properties = { + context, + w: w || playerSize[0][0], + h: h || playerSize[0][1], + }; + + for (const param of VIDEO_PARAMS) { + const paramValue = deepAccess(bid, `mediaTypes.video.${param}`); + + if (paramValue) { + payloadObject.video_properties[param] = paramValue; + } + } + + return payloadObject; +} + +function readUserIdFromCookie(key) { + try { + const storedValue = storage.getCookie(key); + + if (storedValue !== null) { + return storedValue; + } + } catch (error) { + } +} + +function generateId() { + return generateUUID(); +} + +function daysToMs(days) { + return days * DAY_IN_MS; +} + +function writeIdToCookie(key, value) { + if (storage.cookiesAreEnabled()) { + const expires = new Date( + Date.now() + daysToMs(parseInt(COOKIE_TTL_DAYS)) + ).toUTCString(); + storage.setCookie(key, value, expires, '/'); + } +} + +function prepareFirstPartyData({ user, device, site, app, regs }) { + let userData; + let deviceData; + let siteData; + let appData; + let regsData; + + if (user) { + userData = { + yob: user.yob, + gender: user.gender, + }; + } + + if (device) { + deviceData = { + ua: device.ua, + dnt: device.dnt, + lmt: device.lmt, + ip: device.ip, + ipv6: device.ipv6, + devicetype: device.devicetype, + make: device.make, + model: device.model, + os: device.os, + osv: device.osv, + js: device.js, + language: device.language, + carrier: device.carrier, + connectiontype: device.connectiontype, + ifa: device.ifa, + ...(device.geo && { + geo: { + lat: device.geo.lat, + lon: device.geo.lon, + country: device.geo.country, + region: device.geo.region, + regionfips104: device.geo.regionfips104, + metro: device.geo.metro, + city: device.geo.city, + zip: device.geo.zip, + type: device.geo.type, + }, + }), + ...(device.ext && { + ext: { + ifatype: device.ext.ifatype, + }, + }), + }; + } + + if (site) { + siteData = { + id: site.id, + name: site.name, + domain: site.domain, + page: site.page, + cat: site.cat, + sectioncat: site.sectioncat, + pagecat: site.pagecat, + ref: site.ref, + ...(site.publisher && { + publisher: { + name: site.publisher.name, + cat: site.publisher.cat, + domain: site.publisher.domain, + }, + }), + ...(site.content && { + content: { + id: site.content.id, + episode: site.content.episode, + title: site.content.title, + series: site.content.series, + artist: site.content.artist, + genre: site.content.genre, + album: site.content.album, + isrc: site.content.isrc, + season: site.content.season, + }, + }), + mobile: site.mobile, + }; + } + + if (app) { + appData = { + id: app.id, + name: app.name, + bundle: app.bundle, + domain: app.domain, + storeurl: app.storeurl, + cat: app.cat, + sectioncat: app.sectioncat, + pagecat: app.pagecat, + ver: app.ver, + privacypolicy: app.privacypolicy, + paid: app.paid, + ...(app.publisher && { + publisher: { + name: app.publisher.name, + cat: app.publisher.cat, + domain: app.publisher.domain, + }, + }), + keywords: app.keywords, + ...(app.content && { + content: { + id: app.content.id, + episode: app.content.episode, + title: app.content.title, + series: app.content.series, + artist: app.content.artist, + genre: app.content.genre, + album: app.content.album, + isrc: app.content.isrc, + season: app.content.season, + }, + }), + }; + } + + if (regs) { + regsData = { coppa: regs.coppa }; + } + + return cleanObject({ + user: userData, + device: deviceData, + site: siteData, + app: appData, + regs: regsData, + }); +} + +function cleanObject(data) { + for (let key in data) { + if (typeof data[key] == 'object') { + cleanObject(data[key]); + + if (isEmpty(data[key])) delete data[key]; + } + + if (data[key] === undefined) delete data[key]; + } + + return data; +} + +function isVideoRequestValid(bidRequest) { + if (bidRequest.mediaTypes.video) { + const { w, h, playerSize, mimes, protocols } = deepAccess( + bidRequest, + 'mediaTypes.video', + {} + ); + + const areSizesValid = + (isNumber(w) && isNumber(h)) || validateSizes(playerSize); + const areMimesValid = isArray(mimes) && mimes.length > 0; + const areProtocolsValid = + isArray(protocols) && protocols.length > 0 && protocols.every(isNumber); + + return areSizesValid && areMimesValid && areProtocolsValid; + } + + return true; +} + +function validateSizes(sizes) { + return ( + isArray(sizes) && + sizes.length > 0 && + sizes.every( + (size) => isArray(size) && size.length === 2 && size.every(isNumber) + ) + ); +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.floorPrice ? bid.params.floorPrice : 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/pstudioBidAdapter.md b/modules/pstudioBidAdapter.md new file mode 100644 index 00000000000..0c8c6927f43 --- /dev/null +++ b/modules/pstudioBidAdapter.md @@ -0,0 +1,148 @@ +# Overview + +``` +Module Name: PStudio Bid Adapter +Module Type: Bidder Adapter +Maintainer: pstudio@telkomsel.com +``` + +# Description + +Currently module supports banner as well as instream video mediaTypes. + + +# Test parameters + +Those parameters should be used to get test responses from the adapter. + +```js +var adUnits = [ + // Banner ad unit + { + code: 'test-div-1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'pstudio', + params: { + // id of test publisher + pubid: '22430f9d-9610-432c-aabe-6134256f11af', + floorPrice: 1.25, + }, + }, + ], + }, + // Instream video ad unit + { + code: 'test-div-2', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [3], + }, + }, + bids: [ + { + bidder: 'pstudio', + params: { + // id of test publisher + pubid: '22430f9d-9610-432c-aabe-6134256f11af', + floorPrice: 1.25, + }, + }, + ], + }, +]; +``` + +# Sample Banner Ad Unit + +```js +var adUnits = [ + { + code: 'test-div-1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + pos: 0, + name: 'test-name', + }, + }, + bids: [ + { + bidder: 'pstudio', + params: { + pubid: '22430f9d-9610-432c-aabe-6134256f11af', // required + floorPrice: 1.15, // required + bcat: ['IAB1-1', 'IAB1-3'], // optional + badv: ['nike.com'], // optional + bapp: ['com.foo.mygame'], // optional + }, + }, + ], + }, +]; +``` + +# Sample Video Ad Unit + +```js +var videoAdUnits = [ + { + code: 'test-instream-video', + mediaTypes: { + video: { + context: 'instream', // required (only instream accepted) + playerSize: [640, 480], // required (alternatively it could be pair of `w` and `h` parameters) + mimes: ['video/mp4'], // required (only choices `video/mp4`, `application/javascript`, `video/webm` and `video/ogg` supported) + protocols: [2, 3], // 1 required (only choices 2 and 3 supported) + minduration: 5, // optional + maxduration: 30, // optional + startdelay: 5, // optional + placement: 1, // optional (only 1 accepted, as it is instream placement) + skip: 1, // optional + skipafter: 1, // optional + minbitrate: 10, // optional + maxbitrate: 10, // optional + delivery: 1, // optional + playbackmethod: [1, 3], // optional + api: [2], // optional (only choice 2 supported) + linearity: 1, // optional + }, + }, + bids: [ + { + bidder: 'pstudio', + params: { + pubid: '22430f9d-9610-432c-aabe-6134256f11af', + floorPrice: 1.25, + badv: ['adidas.com'], + }, + }, + ], + }, +]; +``` + +# Configuration for video + +### Prebid cache + +For video ads, Prebid cache must be enabled, as the demand partner does not support caching of video content. + +```js +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache', + }, +}); +``` + +Please provide Prebid Cache of your choice. This example uses AppNexus cache, but if you use other cache, change it according to your needs. + diff --git a/modules/publirBidAdapter.js b/modules/publirBidAdapter.js new file mode 100644 index 00000000000..432e123458c --- /dev/null +++ b/modules/publirBidAdapter.js @@ -0,0 +1,391 @@ +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + timestamp, + triggerPixel, + getDNT, + getBidIdParameter +} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'publir'; +const ADAPTER_VERSION = '1.0.0'; +const TTL = 360; +const CURRENCY = 'USD'; +const DEFAULT_SELLER_ENDPOINT = 'https://prebid.publir.com/publirPrebidEndPoint'; +const DEFAULT_IMPS_ENDPOINT = 'https://prebidimpst.publir.com/publirPrebidImpressionTracker'; +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); +export const spec = { + code: BIDDER_CODE, + version: ADAPTER_VERSION, + aliases: ['plr'], + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params.pubId) { + logWarn('pubId is a mandatory param for Publir adapter'); + return false; + } + + return true; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const reqObj = {}; + const generalObject = validBidRequests[0]; + reqObj.params = generatePubGeneralsParams(generalObject, bidderRequest); + reqObj.bids = generatePubBidParams(validBidRequests, bidderRequest); + reqObj.bids.timestamp = timestamp(); + let options = { + withCredentials: false + }; + + return { + method: 'POST', + url: DEFAULT_SELLER_ENDPOINT, + data: reqObj, + options + } + }, + interpretResponse: function ({ body }) { + const bidResponses = []; + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.creativeId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + }, + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } else { + bidResponse.meta.advertiserDomains = []; + } + if (adUnit?.meta?.ad_key) { + bidResponse.meta.ad_key = adUnit.meta.ad_key ?? null; + } + if (adUnit.campId) { + bidResponse.campId = adUnit.campId; + } + bidResponse.bidder = BIDDER_CODE; + bidResponses.push(bidResponse); + }); + } else { + return []; + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (response.body && response.body.params) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.params.userSyncURL + }); + } + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + } + return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + logInfo('onBidWon:', bid); + ajax(DEFAULT_IMPS_ENDPOINT, null, JSON.stringify(bid), { method: 'POST', mode: 'no-cors', credentials: 'include', headers: { 'Content-Type': 'application/json' } }); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + }, +}; + +registerBidder(spec); + +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid, mediaType) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: CURRENCY, + mediaType: mediaType, + size: '*' + }); + return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; +} + +/** + * Get the the ad sizes array from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizesArray(bid, mediaType) { + let sizesArray = [] + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizesArray = bid.sizes; + } + + return sizesArray; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !isEmpty(val) ? encodeURIComponent(val) : ''; +} + +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +/** + * get device type + * @param ua {ua} + * @returns {string} + */ +function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(ua.toLowerCase())) { + return '5'; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(ua.toLowerCase())) { + return '4'; + } + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i.test(ua.toLowerCase())) { + return '3'; + } + return '1'; +} + +function generatePubBidParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + return bidsArray; +} + +/** + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object + */ +function generateBidParameters(bid, bidderRequest) { + const { params } = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), + transactionId: bid.ortb2Imp?.ext?.tid, + coppa: 0 + }; + + const pubId = params.pubId; + if (pubId) { + bidObject.pubId = pubId; + } + + const sua = deepAccess(bid, `ortb2.device.sua`); + if (sua) { + bidObject.sua = sua; + } + + const coppa = deepAccess(bid, `ortb2.regs.coppa`) + if (coppa) { + bidObject.coppa = 1; + } + + return bidObject; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +function getLocalStorage(cookieObjName) { + if (storage.localStorageIsEnabled()) { + const lstData = storage.getDataFromLocalStorage(cookieObjName); + return lstData; + } + return ''; +} + +/** + * Generate params that are common between all bids + * @param generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object + */ +function generatePubGeneralsParams(generalObject, bidderRequest) { + const domain = bidderRequest.refererInfo; + const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; + const { bidderCode } = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = bidderRequest.timeout; + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + auction_start: bidderRequest.auctionStart, + publisher_id: generalBidParams.pubId, + publisher_name: domain, + site_domain: domain, + dnt: getDNT() ? 1 : 0, + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), + tmax: timeout, + user_cookie: getLocalStorage('_publir_prebid_creative') + }; + + const userIdsParam = getBidIdParameter('userId', generalObject); + if (userIdsParam) { + generalParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = bidderRequest.ortb2 || {}; + if (ortb2Metadata.site) { + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + generalParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + generalParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + } + + return generalParams; +} diff --git a/modules/publirBidAdapter.md b/modules/publirBidAdapter.md new file mode 100644 index 00000000000..872fd40c2ae --- /dev/null +++ b/modules/publirBidAdapter.md @@ -0,0 +1,47 @@ +# Overview + +``` +Module Name: Publir Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@publir.com +``` + + +# Description + +Module that connects to Publir's demand sources. + +The Publir adapter requires setup and approval from the Publir. Please reach out to info@publir.com to create an Publir account. + +The adapter supports Video(instream). + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `pubId` | required | String | Publir publisher Id provided by your Publir representative | "1234567890abcdef12345678" + + +# Test Parameters +```javascript +var adUnits = [ + { + code: 'hre_div-hre-vcn-1', + sizes: [[1080, 1920]]], + mediaTypes: { + banner: { + sizes: [ + [1080, 1920], + ], + }, + }, + bids: [{ + bidder: 'publir', + params: { + pubId: '1234567890abcdef12345678' + } + }] + } + ]; +``` diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index ad2a06ea86d..ced47086f7b 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -151,6 +151,7 @@ function parseBidResponse(bid) { 'cpm', () => window.parseFloat(Number(bid.cpm).toFixed(BID_PRECISION)), 'originalCpm', () => window.parseFloat(Number(bid.originalCpm).toFixed(BID_PRECISION)), 'originalCurrency', + 'adserverTargeting', 'dealChannel', 'meta', 'status', @@ -278,6 +279,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { if (isOWPubmaticBid(adapterName) && isS2SBidder(bid.bidder)) { return; } + const pg = window.parseFloat(Number(bid.bidResponse?.adserverTargeting?.hb_pb || bid.bidResponse?.adserverTargeting?.pwtpb).toFixed(BID_PRECISION)); partnerBids.push({ 'pn': adapterName, 'bc': bid.bidderCode || bid.bidder, @@ -303,7 +305,8 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING, 'frv': bid.bidResponse ? bid.bidResponse.floorData?.floorRuleValue : undefined, - 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined + 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined, + 'pb': pg || undefined }); }); return partnerBids; @@ -391,7 +394,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { // getGptSlotInfoForAdUnitCode returns gptslot corresponding to adunit provided as input. let slotObject = { 'sn': adUnitId, - 'au': origAdUnit.adUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId, + 'au': origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId, 'mt': getAdUnitAdFormats(origAdUnit), 'sz': getSizesForAdUnit(adUnit, adUnitId), 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), @@ -450,14 +453,16 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { return; } let origAdUnit = getAdUnit(cache.auctions[auctionId].origAdUnits, adUnitId) || {}; + let owAdUnitId = origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId; let auctionCache = cache.auctions[auctionId]; let floorData = auctionCache.floorData; let wiid = cache.auctions[auctionId]?.wiid || auctionId; let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; let adv = winningBid.bidResponse ? getAdDomain(winningBid.bidResponse) || undefined : undefined; let fskp = floorData ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined; - + let pg = window.parseFloat(Number(winningBid?.bidResponse?.adserverTargeting?.hb_pb || winningBid?.bidResponse?.adserverTargeting?.pwtpb)) || undefined; let pixelURL = END_POINT_WIN_BID_LOGGER; + pixelURL += 'pubid=' + publisherId; pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''); pixelURL += '&tst=' + Math.round((new window.Date()).getTime() / 1000); @@ -466,7 +471,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&pid=' + enc(profileId); pixelURL += '&pdvid=' + enc(profileVersionId); pixelURL += '&slot=' + enc(adUnitId); - pixelURL += '&au=' + enc(origAdUnit.adUnitId || adUnitId); + pixelURL += '&au=' + enc(owAdUnitId); pixelURL += '&pn=' + enc(adapterName); pixelURL += '&bc=' + enc(winningBid.bidderCode || winningBid.bidder); pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); @@ -474,6 +479,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); pixelURL += '&piid=' + enc(winningBid.bidResponse.partnerImpId || EMPTY_STRING); pixelURL += '&di=' + enc(winningBid?.bidResponse?.dealId || OPEN_AUCTION_DEAL_ID); + pixelURL += '&pb=' + enc(pg); pixelURL += '&plt=' + enc(getDevicePlatform()); pixelURL += '&psz=' + enc((winningBid?.bidResponse?.dimensions?.width || '0') + 'x' + diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index d25627a7b90..f28feaa534d 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1010,6 +1010,10 @@ export function prepareMetaObject(br, bid, seat) { br.meta.secondaryCatIds = bid.cat; br.meta.primaryCatId = bid.cat[0]; } + + if (bid.ext && bid.ext.dsa && Object.keys(bid.ext.dsa).length) { + br.meta.dsa = bid.ext.dsa; + } } export const spec = { @@ -1070,7 +1074,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { @@ -1217,6 +1221,11 @@ export const spec = { deepSetValue(payload, 'regs.coppa', 1); } + // dsa + if (bidderRequest?.ortb2?.regs?.ext?.dsa) { + deepSetValue(payload, 'regs.ext.dsa', bidderRequest.ortb2.regs.ext.dsa); + } + _handleEids(payload, validBidRequests); // First Party Data @@ -1396,6 +1405,7 @@ export const spec = { } catch (error) { logError(error); } + return bidResponses; }, diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index 507df4a2bb0..eca0c971050 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -180,7 +180,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { diff --git a/modules/rasBidAdapter.js b/modules/rasBidAdapter.js index 4e93f2aa8eb..74abd0fb4a1 100644 --- a/modules/rasBidAdapter.js +++ b/modules/rasBidAdapter.js @@ -1,8 +1,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { isEmpty, parseSizesInput, deepAccess } from '../src/utils.js'; -import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { + isEmpty, + parseSizesInput, + deepAccess +} from '../src/utils.js'; +import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; +import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; const BIDDER_CODE = 'ras'; const VERSION = '1.0'; @@ -56,28 +60,156 @@ function parseParams(params, bidderRequest) { } } } + if (bidderRequest?.ortb2?.regs?.ext?.dsa?.required !== undefined) { + newParams.dsainfo = bidderRequest?.ortb2?.regs?.ext?.dsa?.required; + } return newParams; } -const buildBid = (ad) => { - if (ad.type === 'empty') { +/** + * @param url string + * @param type number // 1 - img, 2 - js + * @returns an object { event: 1, method: 1 or 2, url: 'string' } + */ +function prepareItemEventtrackers(url, type) { + return { + event: 1, + method: type, + url: url + }; +} + +function prepareEventtrackers(emsLink, imp, impression, impression1, impressionJs1) { + const eventtrackers = [prepareItemEventtrackers(emsLink, 1)]; + + if (imp) { + eventtrackers.push(prepareItemEventtrackers(imp, 1)); + } + + if (impression) { + eventtrackers.push(prepareItemEventtrackers(impression, 1)); + } + + if (impression1) { + eventtrackers.push(prepareItemEventtrackers(impression1, 1)); + } + + if (impressionJs1) { + eventtrackers.push(prepareItemEventtrackers(impressionJs1, 2)); + } + + return eventtrackers; +} + +function parseOrtbResponse(ad) { + if (!(ad.data?.fields && ad.data?.meta)) { + return false; + } + + const { image, Image, title, url, Headline, Thirdpartyclicktracker, imp, impression, impression1, impressionJs1 } = ad.data.fields; + const { dsaurl, height, width, adclick } = ad.data.meta; + const emsLink = ad.ems_link; + const link = adclick + (url || Thirdpartyclicktracker); + const eventtrackers = prepareEventtrackers(emsLink, imp, impression, impression1, impressionJs1); + const ortb = { + ver: '1.2', + assets: [ + { + id: 2, + img: { + url: image || Image || '', + w: width, + h: height + } + }, + { + id: 4, + title: { + text: title || Headline || '' + } + }, + { + id: 3, + data: { + value: deepAccess(ad, 'data.meta.advertiser_name', null), + type: 1 + } + } + ], + link: { + url: link + }, + eventtrackers + }; + + if (dsaurl) { + ortb.privacy = dsaurl + } + + return ortb +} + +function parseNativeResponse(ad) { + if (!(ad.data?.fields && ad.data?.meta)) { + return false; + } + + const { image, Image, title, leadtext, url, Calltoaction, Body, Headline, Thirdpartyclicktracker } = ad.data.fields; + const { dsaurl, height, width, adclick } = ad.data.meta; + const link = adclick + (url || Thirdpartyclicktracker); + const nativeResponse = { + sendTargetingKeys: false, + title: title || Headline || '', + image: { + url: image || Image || '', + width, + height + }, + + clickUrl: link, + cta: Calltoaction || '', + body: leadtext || Body || '', + sponsoredBy: deepAccess(ad, 'data.meta.advertiser_name', null) || '', + ortb: parseOrtbResponse(ad) + }; + + if (dsaurl) { + nativeResponse.privacyLink = dsaurl; + } + + return nativeResponse +} + +const buildBid = (ad, mediaType) => { + if (ad.type === 'empty' || mediaType === undefined) { return null; } - return { + + const data = { requestId: ad.id, cpm: ad.bid_rate ? ad.bid_rate.toFixed(2) : 0, - width: ad.width || 0, - height: ad.height || 0, ttl: 300, creativeId: ad.adid ? parseInt(ad.adid.split(',')[2], 10) : 0, netRevenue: true, currency: ad.currency || 'USD', dealId: null, - meta: { - mediaType: BANNER - }, - ad: ad.html || null - }; + actgMatch: ad.actg_match || 0, + meta: { mediaType: BANNER }, + mediaType: BANNER, + ad: ad.html || null, + width: ad.width || 0, + height: ad.height || 0 + } + + if (mediaType === 'native') { + data.meta = { mediaType: NATIVE }; + data.mediaType = NATIVE; + data.native = parseNativeResponse(ad) || {}; + + delete data.ad; + } + + return data; }; const getContextParams = (bidRequests, bidderRequest) => { @@ -102,18 +234,24 @@ const getSlots = (bidRequests) => { for (let i = 0; i < batchSize; i++) { const adunit = bidRequests[i]; const slotSequence = deepAccess(adunit, 'params.slotSequence'); - - const sizes = parseSizesInput(getAdUnitSizes(adunit)).join(','); + const creFormat = getAdUnitCreFormat(adunit); + const sizes = creFormat === 'native' ? 'fluid' : parseSizesInput(getAdUnitSizes(adunit)).join(','); queryString += `&slot${i}=${encodeURIComponent(adunit.params.slot)}&id${i}=${encodeURIComponent(adunit.bidId)}&composition${i}=CHILD`; - if (sizes.length) { + if (creFormat === 'native') { + queryString += `&cre_format${i}=native`; + } + + if (sizes) { queryString += `&iusizes${i}=${encodeURIComponent(sizes)}`; } - if (slotSequence !== undefined) { + + if (slotSequence !== undefined && slotSequence !== null) { queryString += `&pos${i}=${encodeURIComponent(slotSequence)}`; } } + return queryString; }; @@ -160,9 +298,24 @@ const parseAuctionConfigs = (serverResponse, bidRequest) => { } } +const getAdUnitCreFormat = (adUnit) => { + if (!adUnit) { + return; + } + + let creFormat = 'html'; + let mediaTypes = Object.keys(adUnit.mediaTypes); + + if (mediaTypes && mediaTypes.length === 1 && mediaTypes.includes('native')) { + creFormat = 'native'; + } + + return creFormat; +} + export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE], isBidRequestValid: function (bidRequest) { if (!bidRequest || !bidRequest.params || typeof bidRequest.params !== 'object') { @@ -183,7 +336,8 @@ export const spec = { bidId: bid.bidId, sizes: getAdUnitSizes(bid), params: bid.params, - fledgeEnabled: fledgeEligible + fledgeEnabled: fledgeEligible, + mediaType: (bid.mediaTypes && bid.mediaTypes.banner) ? 'display' : NATIVE })); return [{ @@ -195,9 +349,11 @@ export const spec = { interpretResponse: function (serverResponse, bidRequest) { const response = serverResponse.body; - const fledgeAuctionConfigs = parseAuctionConfigs(serverResponse, bidRequest); - const bids = (!response || !response.ads || response.ads.length === 0) ? [] : response.ads.map(buildBid).filter((bid) => !isEmpty(bid)); + const bids = (!response || !response.ads || response.ads.length === 0) ? [] : response.ads.map((ad, index) => buildBid( + ad, + bidRequest?.bidIds?.[index]?.mediaType || 'banner' + )).filter((bid) => !isEmpty(bid)); if (fledgeAuctionConfigs) { // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. diff --git a/modules/rasBidAdapter.md b/modules/rasBidAdapter.md index e8a61974130..cf169fedb63 100644 --- a/modules/rasBidAdapter.md +++ b/modules/rasBidAdapter.md @@ -9,7 +9,7 @@ Maintainer: support@ringpublishing.com # Description Module that connects to Ringer Axel Springer demand sources. -Only banner format is supported. +Only banner and native format is supported. # Test Parameters ```js @@ -49,4 +49,4 @@ var adUnits = [{ | pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}` | | pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"` | | pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"` | -| customParams | optional | Object | Custom request params | `{}` | \ No newline at end of file +| customParams | optional | Object | Custom request params | `{}` | diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index cfad8fce966..1cd97696770 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -165,6 +165,7 @@ export const spec = { interpretResponse: function (serverResponse, originalRequest) { let bids; + const fledgeInterestGroupBuyers = config.getConfig('fledgeConfig.interestGroupBuyers') || []; const responseBody = serverResponse.body; let fledgeAuctionConfigs = null; @@ -186,7 +187,7 @@ export const spec = { { seller, decisionLogicUrl, - interestGroupBuyers: Object.keys(perBuyerSignals), + interestGroupBuyers: [...fledgeInterestGroupBuyers, ...Object.keys(perBuyerSignals)], perBuyerSignals, }, sellerTimeout diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 8968d4c795a..c5308c91e18 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -1,6 +1,7 @@ /** * This module adds Real time data support to prebid.js * @module modules/realTimeData + * @typedef {import('../../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig */ /** @@ -30,7 +31,7 @@ */ /** - * @function? + * @function * @summary return real time data * @name RtdSubmodule#getTargetingData * @param {string[]} adUnitsCodes @@ -40,7 +41,7 @@ */ /** - * @function? + * @function * @summary modify bid request data * @name RtdSubmodule#getBidRequestData * @param {Object} reqBidsConfigObj @@ -73,7 +74,7 @@ */ /** - * @function? + * @function * @summary on auction init event * @name RtdSubmodule#onAuctionInitEvent * @param {Object} data @@ -82,7 +83,7 @@ */ /** - * @function? + * @function * @summary on auction end event * @name RtdSubmodule#onAuctionEndEvent * @param {Object} data @@ -91,7 +92,7 @@ */ /** - * @function? + * @function * @summary on bid response event * @name RtdSubmodule#onBidResponseEvent * @param {Object} data @@ -100,7 +101,7 @@ */ /** - * @function? + * @function * @summary on bid requested event * @name RtdSubmodule#onBidRequestEvent * @param {Object} data @@ -109,7 +110,7 @@ */ /** - * @function? + * @function * @summary on data deletion request * @name RtdSubmodule#onDataDeletionRequest * @param {SubmoduleConfig} config diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index c4f6e7d545d..c03065cd5a5 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -201,9 +201,6 @@ export const converter = ortbConverter({ const imp = buildImp(bidRequest, context); imp.id = bidRequest.adUnitCode; delete imp.banner; - if (config.getConfig('s2sConfig.defaultTtl')) { - imp.exp = config.getConfig('s2sConfig.defaultTtl'); - }; bidRequest.params.position === 'atf' && imp.video && (imp.video.pos = 1); bidRequest.params.position === 'btf' && imp.video && (imp.video.pos = 3); delete imp.ext?.prebid?.storedrequest; @@ -237,7 +234,7 @@ export const converter = ortbConverter({ }, context: { netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true - ttl: 300, + ttl: 360, }, processors: pbsExtensions }); @@ -685,7 +682,7 @@ export const spec = { creativeId: ad.creative_id || `${ad.network || ''}-${ad.advertiser || ''}`, cpm: ad.cpm || 0, dealId: ad.deal, - ttl: 300, // 5 minutes + ttl: 360, // 6 minutes netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true rubicon: { advertiserId: ad.advertiser, networkId: ad.network diff --git a/modules/setupadBidAdapter.js b/modules/setupadBidAdapter.js new file mode 100644 index 00000000000..55677d51c56 --- /dev/null +++ b/modules/setupadBidAdapter.js @@ -0,0 +1,271 @@ +import { + _each, + createTrackPixelHtml, + deepAccess, + isStr, + getBidIdParameter, + triggerPixel, + logWarn, +} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'setupad'; +const ENDPOINT = 'https://prebid.setupad.io/openrtb2/auction'; +const SYNC_ENDPOINT = 'https://cookie.stpd.cloud/sync?'; +const REPORT_ENDPOINT = 'https://adapter-analytics.setupad.io/api/adapter-analytics'; +const GVLID = 1241; +const TIME_TO_LIVE = 360; +const biddersCreativeIds = {}; + +function getEids(bidRequest) { + if (deepAccess(bidRequest, 'userIdAsEids')) return bidRequest.userIdAsEids; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + gvlid: GVLID, + + isBidRequestValid: function (bid) { + return !!(bid.params.placement_id && isStr(bid.params.placement_id)); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const requests = []; + + _each(validBidRequests, function (bid) { + const id = getBidIdParameter('placement_id', bid.params); + const accountId = getBidIdParameter('account_id', bid.params); + const auctionId = bid.auctionId; + const bidId = bid.bidId; + const eids = getEids(bid) || undefined; + let sizes = bid.sizes; + if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; + + const site = { + page: bidderRequest?.refererInfo?.page, + ref: bidderRequest?.refererInfo?.ref, + domain: bidderRequest?.refererInfo?.domain, + }; + const device = { + w: bidderRequest?.ortb2?.device?.w, + h: bidderRequest?.ortb2?.device?.h, + }; + + const payload = { + id: bid?.bidderRequestId, + ext: { + prebid: { + storedrequest: { + id: accountId || 'default', + }, + }, + }, + user: { ext: { eids } }, + device, + site, + imp: [], + }; + + const imp = { + id: bid.adUnitCode, + ext: { + prebid: { + storedrequest: { id }, + }, + }, + }; + + if (deepAccess(bid, 'mediaTypes.banner')) { + imp.banner = { + format: (sizes || []).map((s) => { + return { w: s[0], h: s[1] }; + }), + }; + } + + payload.imp.push(imp); + + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const uspConsent = bidderRequest && bidderRequest.uspConsent; + + if (gdprConsent || uspConsent) { + payload.regs = { ext: {} }; + + if (uspConsent) payload.regs.ext.us_privacy = uspConsent; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + payload.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + } + + if (typeof gdprConsent.consentString !== 'undefined') { + payload.user.ext.consent = gdprConsent.consentString; + } + } + } + const params = bid.params; + + requests.push({ + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload), + options: { + contentType: 'text/plain', + withCredentials: true, + }, + + bidId, + params, + auctionId, + }); + }); + + return requests; + }, + + interpretResponse: function (serverResponse, bidRequest) { + if ( + !serverResponse || + !serverResponse.body || + typeof serverResponse.body != 'object' || + Object.keys(serverResponse.body).length === 0 + ) { + logWarn('no response or body is malformed'); + return []; + } + + const serverBody = serverResponse.body; + const bidResponses = []; + + _each(serverBody.seatbid, (res) => { + _each(res.bid, (bid) => { + const requestId = bidRequest.bidId; + const params = bidRequest.params; + const { ad, adUrl } = getAd(bid); + + const bidResponse = { + requestId, + params, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.id, + currency: serverBody.cur, + netRevenue: true, + ttl: TIME_TO_LIVE, + meta: { + advertiserDomains: bid.adomain || [], + }, + }; + + // set a seat for creativeId for triggerPixel url + biddersCreativeIds[bidResponse.creativeId] = res.seat; + + bidResponse.ad = ad; + bidResponse.adUrl = adUrl; + bidResponses.push(bidResponse); + }); + }); + + return bidResponses; + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + if (!responses?.length) return []; + + const syncs = []; + const bidders = getBidders(responses); + + if (syncOptions.iframeEnabled && bidders) { + const queryParams = []; + + queryParams.push(`bidders=${bidders}`); + queryParams.push('gdpr=' + +gdprConsent.gdprApplies); + queryParams.push('gdpr_consent=' + gdprConsent.consentString); + queryParams.push('usp_consent=' + (uspConsent || '')); + + const strQueryParams = queryParams.join('&'); + + syncs.push({ + type: 'iframe', + url: SYNC_ENDPOINT + strQueryParams + '&type=iframe', + }); + + return syncs; + } + + return []; + }, + + onBidWon: function (bid) { + let bidder = bid.bidder || bid.bidderCode; + const auctionId = bid.auctionId; + if (bidder !== BIDDER_CODE) return; + + let params; + if (bid.params) { + params = Array.isArray(bid.params) ? bid.params : [bid.params]; + } else { + if (Array.isArray(bid.bids)) { + params = bid.bids.map((singleBid) => singleBid.params); + } + } + + if (!params?.length) return; + + const placementIdsArray = []; + params.forEach((param) => { + if (!param.placement_id) return; + placementIdsArray.push(param.placement_id); + }); + + const placementIds = (placementIdsArray.length && placementIdsArray.join(';')) || ''; + + if (!placementIds) return; + + let extraBidParams = ''; + + // find the winning bidder by using creativeId as identification + if (biddersCreativeIds.hasOwnProperty(bid.creativeId) && biddersCreativeIds[bid.creativeId]) { + bidder = biddersCreativeIds[bid.creativeId]; + } + + // Add extra parameters + extraBidParams = `&cpm=${bid.originalCpm}¤cy=${bid.originalCurrency}`; + + const url = `${REPORT_ENDPOINT}?event=bidWon&bidder=${bidder}&placementIds=${placementIds}&auctionId=${auctionId}${extraBidParams}×tamp=${Date.now()}`; + triggerPixel(url); + }, +}; + +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 getAd(bid) { + let ad, adUrl; + + switch (deepAccess(bid, 'ext.prebid.type')) { + default: + if (bid.adm && bid.nurl) { + ad = bid.adm; + ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } else if (bid.adm) { + ad = bid.adm; + } else if (bid.nurl) { + adUrl = bid.nurl; + } + } + + return { ad, adUrl }; +} + +registerBidder(spec); diff --git a/modules/setupadBidAdapter.md b/modules/setupadBidAdapter.md new file mode 100644 index 00000000000..0d4f0ef392e --- /dev/null +++ b/modules/setupadBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +```text +Module Name: Setupad Bid Adapter +Module Type: Bidder Adapter +Maintainer: it@setupad.com +``` + +# Description + +Module that connects to Setupad's demand sources. + +# Test Parameters + +```js +const adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'setupad', + params: { + placement_id: '123', //required + account_id: '123', //optional + }, + }, + ], + }, +]; +``` diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index ae1fb131966..2264bc37ebb 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -92,6 +92,10 @@ export const sharethroughAdapterSpec = { req.regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; } + if (bidderRequest?.ortb2?.regs?.ext?.dsa) { + req.regs.ext.dsa = bidderRequest.ortb2.regs.ext.dsa; + } + const imps = bidRequests .map((bidReq) => { const impression = { ext: {} }; diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 2409bebbc59..6920983e50d 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -41,8 +41,16 @@ function getAdUrlByRegion(bid) { try { const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const region = timezone.split('/')[0]; - if (region === 'Europe') adUrl = adUrls['EU']; - else adUrl = adUrls['US_EAST']; + + switch (region) { + case 'Europe': + adUrl = adUrls['EU']; + break; + case 'Asia': + adUrl = adUrls['SGP']; + break; + default: adUrl = adUrls['US_EAST']; + } } catch (err) { adUrl = adUrls['US_EAST']; } @@ -74,6 +82,7 @@ export const spec = { location = winTop.location; logMessage(e); }; + let placements = []; let request = { 'deviceWidth': winTop.screen.width, @@ -83,7 +92,9 @@ export const spec = { 'host': location.host, 'page': location.pathname, 'coppa': config.getConfig('coppa') === true ? 1 : 0, - 'placements': placements + 'placements': placements, + 'eeid': validBidRequests[0]?.userIdAsEids, + 'ifa': bidderRequest?.ortb2?.device?.ifa, }; request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) if (bidderRequest) { @@ -103,8 +114,10 @@ export const spec = { for (let i = 0; i < len; i++) { let bid = validBidRequests[i]; + if (i === 0) adUrl = getAdUrlByRegion(bid); - let traff = bid.params.traffic || BANNER + + let traff = bid.params.traffic || BANNER; placements.push({ placementId: bid.params.sourceid, bidId: bid.bidId, diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index 515aae0e092..7d4a4bca615 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -11,6 +11,10 @@ const BIDDER_CODE = 'smilewanted'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ const GVL_ID = 639; diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index e786095874e..64604618680 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -107,18 +107,11 @@ export const spec = { } iv = iv || getBidIdParameter('iv', bid.params) - const floorInfo = (bid.getFloor && typeof bid.getFloor === 'function') ? bid.getFloor({ - currency: 'USD', - mediaType: bid.mediaTypes && bid.mediaTypes.banner ? 'banner' : 'video', - size: '*' - }) : {} - floorInfo.floor = floorInfo.floor || getBidIdParameter('bidfloor', bid.params) - const imp = { adunitcode: bid.adUnitCode, id: bid.bidId, tagid: String(getBidIdParameter('tagid', bid.params)), - bidfloor: floorInfo.floor + bidfloor: _getBidFloors(bid) } if (deepAccess(bid, 'mediaTypes.banner')) { @@ -332,4 +325,18 @@ function _buildVideoRequestObj(bid) { return videoObj } +function _getBidFloors(bid) { + const floorInfo = (bid.getFloor && typeof bid.getFloor === 'function') ? bid.getFloor({ + currency: 'USD', + mediaType: bid.mediaTypes && bid.mediaTypes.banner ? 'banner' : 'video', + size: '*' + }) : {} + const floorModuleValue = parseFloat(floorInfo.floor) + if (!isNaN(floorModuleValue)) { + return floorModuleValue + } + const paramValue = parseFloat(getBidIdParameter('bidfloor', bid.params)) + return !isNaN(paramValue) ? paramValue : undefined +} + registerBidder(spec) diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index c351b76d7ea..08b25abee01 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -12,7 +12,7 @@ const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; const GVLID = 676; const TMAX = 450; -const BIDDER_VERSION = '5.92'; +const BIDDER_VERSION = '5.93'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -77,7 +77,7 @@ const getContentLanguage = () => { /** * Get Bid parameters - returns bid params from Object, or 1el array - * @param {*} bidData - bid (bidWon), or array of bids (timeout) + * @param {*} bidParams - bid (bidWon), or array of bids (timeout) * @returns {object} params object */ const unpackParams = (bidParams) => { @@ -216,8 +216,11 @@ const applyTopics = (validBidRequest, ortbRequest) => { }; const applyUserIds = (validBidRequest, ortbRequest) => { - const eids = validBidRequest.userIdAsEids - if (eids && eids.length) { + const { userIdAsEids: eidsVbr = [], ortb2 = {} } = validBidRequest; + const eidsOrtb = ortb2.user?.ext?.data?.eids || []; + const eids = [...eidsVbr, ...eidsOrtb]; + + if (eids.length) { const ids = { eids }; ortbRequest.user = { ...ortbRequest.user, ...ids }; } @@ -243,7 +246,7 @@ const applyGdpr = (bidderRequest, ortbRequest) => { * returns floor = 0 if getFloor() is not defined * * @param {object} slot bid request adslot - * @returns {float} floorprice + * @returns {number} floorprice */ const getHighestFloor = (slot) => { const currency = getCurrency(); @@ -570,6 +573,7 @@ const parseNative = (nativeData, adUnitCode) => { } const renderCreative = (site, auctionId, bid, seat, request) => { + const { adLabel, id, slot, sn, page, publisherId, ref } = site; let gam; const mcad = { @@ -619,16 +623,16 @@ const renderCreative = (site, auctionId, bid, seat, request) => { }
Ad Content
'); + }); + }); +}); diff --git a/test/spec/modules/adstirBidAdapter_spec.js b/test/spec/modules/adstirBidAdapter_spec.js index 290a6822f69..a62dce8af97 100644 --- a/test/spec/modules/adstirBidAdapter_spec.js +++ b/test/spec/modules/adstirBidAdapter_spec.js @@ -166,6 +166,7 @@ describe('AdstirAdapter', function () { expect(d.ref.tloc).to.equal(bidderRequest.refererInfo.topmostLocation); expect(d.ref.referrer).to.equal(bidderRequest.refererInfo.ref); expect(d.sua).to.equal(null); + expect(d.user).to.equal(null); expect(d.gdpr).to.equal(false); expect(d.usp).to.equal(false); expect(d.schain).to.equal(null); diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index 88016d1902c..e317a8828e7 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -1,30 +1,30 @@ import {expect} from 'chai'; import {spec} from 'modules/asoBidAdapter.js'; -import {parseUrl} from 'src/utils.js'; -import {BANNER, VIDEO} from 'src/mediaTypes.js'; +import {BANNER, VIDEO, NATIVE} from 'src/mediaTypes.js'; +import {OUTSTREAM} from 'src/video.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; +import {parseUrl} from '../../../src/utils'; + +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; describe('Adserver.Online bidding adapter', function () { const bannerRequest = { bidder: 'aso', params: { - zone: 1, - attr: { - keywords: ['a', 'b'], - tags: ['t1', 't2'] - } + zone: 1 }, adUnitCode: 'adunit-banner', + bidId: 'bid-banner', mediaTypes: { - banner: { + [BANNER]: { sizes: [ [300, 250], [240, 400], ] } }, - bidId: 'bidid1', - bidderRequestId: 'bidreq1', - auctionId: 'auctionid1', userIdAsEids: [{ source: 'src1', uids: [ @@ -38,32 +38,67 @@ describe('Adserver.Online bidding adapter', function () { const videoRequest = { bidder: 'aso', params: { - zone: 2, - video: { - api: [2], - maxduration: 30 - } + zone: 2 }, + adUnitCode: 'adunit-video', + bidId: 'bid-video', mediaTypes: { - video: { - context: 'outstream', + [VIDEO]: { + context: OUTSTREAM, playerSize: [[640, 480]], protocols: [1, 2], mimes: ['video/mp4'], } + } + }; + + const nativeOrtbRequest = { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }] + }; + + const nativeRequest = { + bidder: 'aso', + params: { + zone: 3 + }, + adUnitCode: 'adunit-native', + bidId: 'bid-native', + mediaTypes: { + [NATIVE]: { + ortb: { + ...nativeOrtbRequest + } + } }, - adUnitCode: 'adunit-video', - bidId: 'bidid12', - bidderRequestId: 'bidreq2', - auctionId: 'auctionid12' + nativeOrtbRequest }; const bidderRequest = { refererInfo: { - numIframes: 0, + page: 'https://example.com/page.html', + topmostLocation: 'https://example.com/page.html', reachedTop: true, - page: 'https://example.com', - domain: 'example.com' + numIframes: 1, + stack: [ + 'https://example.com/page.html', + 'https://example.com/iframe1.html' + ] } }; @@ -81,6 +116,14 @@ describe('Adserver.Online bidding adapter', function () { } }; + const gdprNotApplies = { + gdprApplies: false, + consentString: '', + vendorData: { + purpose: {} + } + }; + const uspConsent = 'usp_consent'; describe('isBidRequestValid', function () { @@ -110,81 +153,121 @@ describe('Adserver.Online bidding adapter', function () { }); }); - describe('buildRequests', function () { - it('creates a valid banner request', function () { - bannerRequest.getFloor = () => ({ currency: 'USD', floor: 0.5 }); + describe('requests builder', function () { + it('should add bid floor', function () { + const bidRequest = Object.assign({}, bannerRequest); + + bidRequest.getFloor = () => { + return { + currency: 'USD', + floor: 0.5 + } + }; + + const payload = spec.buildRequests([bidRequest], bidderRequest)[0].data; + + expect(payload.imp[0].bidfloor).to.equal(0.5); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); + }); + it('endpoint is valid', function () { const requests = spec.buildRequests([bannerRequest], bidderRequest); expect(requests).to.have.lengthOf(1); const request = requests[0]; expect(request).to.exist; expect(request.method).to.equal('POST'); - const parsedRequestUrl = parseUrl(request.url); - expect(parsedRequestUrl.hostname).to.equal('srv.aso1.net'); - expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); + const parsedUrl = parseUrl(request.url); + expect(parsedUrl.hostname).to.equal('srv.aso1.net'); + expect(parsedUrl.pathname).to.equal('/prebid/bidder'); - const query = parsedRequestUrl.search; + const query = parsedUrl.search; expect(query.pbjs).to.contain('$prebid.version$'); expect(query.zid).to.equal('1'); + }); + it('creates a valid banner request', function () { + const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; expect(request.data).to.exist; const payload = request.data; - expect(payload.site).to.not.equal(null); - expect(payload.site.ref).to.equal(''); - expect(payload.site.page).to.equal('https://example.com'); + expect(payload.site).to.exist; + expect(payload.site.page).to.equal('https://example.com/page.html'); - expect(payload.device).to.not.equal(null); + expect(payload.device).to.exist; expect(payload.device.w).to.equal(window.innerWidth); expect(payload.device.h).to.equal(window.innerHeight); expect(payload.imp).to.have.lengthOf(1); expect(payload.imp[0].tagid).to.equal('adunit-banner'); - expect(payload.imp[0].banner).to.not.equal(null); - expect(payload.imp[0].banner.w).to.equal(300); - expect(payload.imp[0].banner.h).to.equal(250); - expect(payload.imp[0].bidfloor).to.equal(0.5); - expect(payload.imp[0].bidfloorcur).to.equal('USD'); + expect(payload.imp[0].banner).to.not.null; + expect(payload.imp[0].banner.format).to.have.lengthOf(2); + expect(payload.imp[0].banner.format[0].w).to.equal(300); + expect(payload.imp[0].banner.format[0].h).to.equal(250); + expect(payload.imp[0].banner.format[1].w).to.equal(240); + expect(payload.imp[0].banner.format[1].h).to.equal(400); }); - it('creates a valid video request', function () { - const requests = spec.buildRequests([videoRequest], bidderRequest); - expect(requests).to.have.lengthOf(1); - const request = requests[0]; + if (FEATURES.VIDEO) { + it('creates a valid video request', function () { + const requests = spec.buildRequests([videoRequest], syncAddFPDToBidderRequest(bidderRequest)); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; - expect(request).to.exist; - expect(request.method).to.equal('POST'); - const parsedRequestUrl = parseUrl(request.url); - expect(parsedRequestUrl.hostname).to.equal('srv.aso1.net'); - expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); + expect(request).to.exist; + expect(request.data).to.not.be.empty; - const query = parsedRequestUrl.search; - expect(query.pbjs).to.contain('$prebid.version$'); - expect(query.zid).to.equal('2'); + const payload = request.data; - expect(request.data).to.not.be.empty; + expect(payload.site).to.exist; + expect(payload.site.page).to.equal('https://example.com/page.html'); - const payload = request.data; + expect(payload.device).to.exist; + expect(payload.device.w).to.equal(window.innerWidth); + expect(payload.device.h).to.equal(window.innerHeight); - expect(payload.site).to.not.equal(null); - expect(payload.site.ref).to.equal(''); - expect(payload.site.page).to.equal('https://example.com'); + expect(payload.imp).to.have.lengthOf(1); - expect(payload.device).to.not.equal(null); - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.imp[0].tagid).to.equal('adunit-video'); + expect(payload.imp[0].video).to.exist; - expect(payload.imp).to.have.lengthOf(1); + expect(payload.imp[0].video.w).to.equal(640); + expect(payload.imp[0].video.h).to.equal(480); + expect(payload.imp[0].banner).to.not.exist; + }); + } - expect(payload.imp[0].tagid).to.equal('adunit-video'); - expect(payload.imp[0].video).to.not.equal(null); - expect(payload.imp[0].video.w).to.equal(640); - expect(payload.imp[0].video.h).to.equal(480); - expect(payload.imp[0].banner).to.be.undefined; - }); + if (FEATURES.NATIVE) { + it('creates a valid native request', function () { + const requests = spec.buildRequests([nativeRequest], syncAddFPDToBidderRequest(bidderRequest)); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; + expect(request.data).to.not.be.empty; + + const payload = request.data; + + expect(payload.site).to.exist; + expect(payload.site.page).to.equal('https://example.com/page.html'); + + expect(payload.device).to.exist; + expect(payload.device.w).to.equal(window.innerWidth); + expect(payload.device.h).to.equal(window.innerHeight); + + expect(payload.imp).to.have.lengthOf(1); + + expect(payload.imp[0].tagid).to.equal('adunit-native'); + expect(payload.imp[0].native).to.exist; + expect(payload.imp[0].native.request).to.exist; + }); + } }); describe('GDPR/USP compliance', function () { @@ -192,7 +275,7 @@ describe('Adserver.Online bidding adapter', function () { bidderRequest.gdprConsent = gdprConsent; bidderRequest.uspConsent = uspConsent; - const requests = spec.buildRequests([bannerRequest], bidderRequest); + const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -209,7 +292,7 @@ describe('Adserver.Online bidding adapter', function () { bidderRequest.gdprConsent = null; bidderRequest.uspConsent = null; - const requests = spec.buildRequests([bannerRequest], bidderRequest); + const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -226,18 +309,21 @@ describe('Adserver.Online bidding adapter', function () { describe('response handler', function () { const bannerResponse = { body: { - id: 'auctionid1', - bidid: 'bidid1', seatbid: [{ bid: [ { - impid: 'impid1', + impid: 'bid-banner', price: 0.3, crid: 321, adm: '', w: 300, h: 250, adomain: ['example.com'], + ext: { + prebid: { + type: 'banner' + } + } } ] }], @@ -255,18 +341,48 @@ describe('Adserver.Online bidding adapter', function () { const videoResponse = { body: { - id: 'auctionid2', - bidid: 'bidid2', seatbid: [{ bid: [ { - impid: 'impid2', + impid: 'bid-video', price: 0.5, crid: 123, adm: '', adomain: ['example.com'], w: 640, h: 480, + ext: { + prebid: { + type: 'video' + } + } + } + ] + }], + cur: 'USD' + }, + }; + + const nativeResponse = { + body: { + seatbid: [{ + bid: [ + { + impid: 'bid-native', + price: 0.5, + crid: 123, + adm: JSON.stringify({ + assets: [ + {id: 0, title: {text: 'Title'}}, + {id: 1, img: {type: 3, url: 'https://img'}}, + ], + }), + adomain: ['example.com'], + ext: { + prebid: { + type: 'native' + } + } } ] }], @@ -275,47 +391,59 @@ describe('Adserver.Online bidding adapter', function () { }; it('handles banner responses', function () { - bannerRequest.bidRequest = { - mediaType: BANNER - }; - const result = spec.interpretResponse(bannerResponse, bannerRequest); - - expect(result).to.have.lengthOf(1); - - expect(result[0]).to.exist; - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].mediaType).to.equal(BANNER); - expect(result[0].creativeId).to.equal(321); - expect(result[0].cpm).to.be.within(0.1, 0.5); - expect(result[0].ad).to.equal(''); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); - expect(result[0].dealId).to.not.exist; - expect(result[0].meta.advertiserDomains[0]).to.equal('example.com'); + const request = spec.buildRequests([bannerRequest], bidderRequest)[0]; + const bids = spec.interpretResponse(bannerResponse, request); + + expect(bids).to.have.lengthOf(1); + + expect(bids[0]).to.exist; + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].mediaType).to.equal(BANNER); + expect(bids[0].creativeId).to.equal(321); + expect(bids[0].cpm).to.be.within(0.1, 0.5); + expect(bids[0].ad).to.equal(''); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].dealId).to.not.exist; + expect(bids[0].meta.advertiserDomains[0]).to.equal('example.com'); }); - it('handles video responses', function () { - const request = { - bidRequest: videoRequest - }; - request.bidRequest.mediaType = VIDEO; - - const result = spec.interpretResponse(videoResponse, request); - expect(result).to.have.lengthOf(1); - - expect(result[0].width).to.equal(640); - expect(result[0].height).to.equal(480); - expect(result[0].mediaType).to.equal(VIDEO); - expect(result[0].creativeId).to.equal(123); - expect(result[0].cpm).to.equal(0.5); - expect(result[0].vastXml).to.equal(''); - expect(result[0].renderer).to.be.a('object'); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(300); - }); + if (FEATURES.VIDEO) { + it('handles video responses', function () { + const request = spec.buildRequests([videoRequest], bidderRequest)[0]; + const bids = spec.interpretResponse(videoResponse, request); + expect(bids).to.have.lengthOf(1); + + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(480); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].creativeId).to.equal(123); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].vastXml).to.equal(''); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ttl).to.equal(300); + }); + } + + if (FEATURES.NATIVE) { + it('handles native responses', function () { + const request = spec.buildRequests([nativeRequest], bidderRequest)[0]; + const bids = spec.interpretResponse(nativeResponse, request); + expect(bids).to.have.lengthOf(1); + + expect(bids[0].mediaType).to.equal(NATIVE); + expect(bids[0].creativeId).to.equal(123); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ttl).to.equal(300); + + expect(bids[0].native.ortb.assets).to.have.lengthOf(2); + }); + } it('handles empty responses', function () { const response = []; @@ -331,11 +459,27 @@ describe('Adserver.Online bidding adapter', function () { }; it('should return iframe sync option', function () { - expect(spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent)[0].type).to.equal('iframe'); - expect(spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent)[0].url).to.equal( - 'sync_url?gdpr=1&consents_str=consentString&consents=1%2C2&us_privacy=usp_consent&' + const syncs = spec.getUserSyncs(syncOptions, [bannerResponse], gdprConsent, uspConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal( + 'sync_url?gdpr=1&consents_str=consentString&consents=1%2C2&us_privacy=usp_consent' ); }); + + it('should return iframe sync option - gdpr not applies', function () { + const syncs = spec.getUserSyncs(syncOptions, [bannerResponse], gdprNotApplies, uspConsent); + expect(syncs).to.have.lengthOf(1); + + expect(syncs[0].url).to.equal( + 'sync_url?us_privacy=usp_consent' + ); + }); + + it('should return no sync option', function () { + const syncs = spec.getUserSyncs(syncOptions, [videoResponse], gdprNotApplies, uspConsent); + expect(syncs).to.have.lengthOf(0); + }); }); }); }); diff --git a/test/spec/modules/automatadAnalyticsAdapter_spec.js b/test/spec/modules/automatadAnalyticsAdapter_spec.js index e591f7e8e95..a7dd28a8dc0 100644 --- a/test/spec/modules/automatadAnalyticsAdapter_spec.js +++ b/test/spec/modules/automatadAnalyticsAdapter_spec.js @@ -6,6 +6,18 @@ import spec, {self as exports} from 'modules/automatadAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { expect } from 'chai'; +const obj = { + auctionInitHandler: (args) => {}, + bidResponseHandler: (args) => {}, + bidderDoneHandler: (args) => {}, + bidWonHandler: (args) => {}, + noBidHandler: (args) => {}, + auctionDebugHandler: (args) => {}, + bidderTimeoutHandler: (args) => {}, + bidRequestedHandler: (args) => {}, + bidRejectedHandler: (args) => {} +} + const { AUCTION_DEBUG, BID_REQUESTED, @@ -117,20 +129,10 @@ describe('Automatad Analytics Adapter', () => { describe('Behaviour of the adapter when the sdk has loaded', () => { before(() => { spec.enableAnalytics(CONFIG_WITH_DEBUG); - const obj = { - auctionInitHandler: (args) => {}, - bidResponseHandler: (args) => {}, - bidderDoneHandler: (args) => {}, - bidWonHandler: (args) => {}, - noBidHandler: (args) => {}, - auctionDebugHandler: (args) => {}, - bidderTimeoutHandler: (args) => {}, - bidRequestedHandler: (args) => {}, - bidRejectedHandler: (args) => {} - } global.window.atmtdAnalytics = obj - + exports.qBeingUsed = false + exports.qTraversalComplete = undefined Object.keys(obj).forEach((fn) => sandbox.spy(global.window.atmtdAnalytics, fn)) }) beforeEach(() => { @@ -143,8 +145,12 @@ describe('Automatad Analytics Adapter', () => { sandbox.restore(); }); after(() => { + const handlers = global.window.atmtdAnalytics + Object.keys(handlers).forEach((handler) => global.window.atmtdAnalytics[handler].reset()) global.window.atmtdAnalytics = undefined; spec.disableAnalytics(); + exports.qBeingUsed = false + exports.qTraversalComplete = undefined }) it('Should call the auctionInitHandler when the auction init event is fired', () => { @@ -298,6 +304,85 @@ describe('Automatad Analytics Adapter', () => { }); }); + describe('Behaviour of the adapter when the SDK has loaded midway', () => { + before(() => { + spec.enableAnalytics(CONFIG_WITH_DEBUG); + }) + beforeEach(() => { + sandbox = sinon.createSandbox(); + + global.window.atmtdAnalytics = undefined + + exports.qBeingUsed = undefined + exports.qTraversalComplete = undefined + exports.queuePointer = 0 + exports.retryCount = 0 + exports.__atmtdAnalyticsQueue.length = 0 + + clock = sandbox.useFakeTimers(); + + sandbox.spy(exports.__atmtdAnalyticsQueue, 'push') + }); + afterEach(() => { + sandbox.restore(); + }); + after(() => { + spec.disableAnalytics(); + }) + + it('Should push to the que when the auctionInit event is fired and push to the que even after SDK has loaded after auctionInit event', () => { + events.emit(BID_RESPONSE, {type: BID_RESPONSE}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(BID_RESPONSE) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(BID_RESPONSE) + expect(exports.qBeingUsed).to.equal(true) + expect(exports.qTraversalComplete).to.equal(undefined) + global.window.atmtdAnalytics = obj + events.emit(BID_RESPONSE, {type: BID_RESPONSE}) + expect(exports.__atmtdAnalyticsQueue.push.calledTwice).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[1]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[1][0]).to.equal(BID_RESPONSE) + expect(exports.__atmtdAnalyticsQueue[1][1].type).to.equal(BID_RESPONSE) + expect(exports.qBeingUsed).to.equal(true) + expect(exports.qTraversalComplete).to.equal(undefined) + }); + + it('Should push to the que when the auctionInit event is fired and push to the analytics adapter handler after the que is processed', () => { + expect(exports.qBeingUsed).to.equal(undefined) + events.emit(AUCTION_INIT, {type: AUCTION_INIT}) + global.window.atmtdAnalytics = {...obj} + const handlers = global.window.atmtdAnalytics + Object.keys(handlers).forEach((handler) => global.window.atmtdAnalytics[handler].reset()) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(AUCTION_INIT) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(AUCTION_INIT) + expect(exports.qBeingUsed).to.equal(true) + expect(exports.qTraversalComplete).to.equal(undefined) + expect(global.window.atmtdAnalytics.auctionInitHandler.callCount).to.equal(0) + clock.tick(2000) + expect(exports.qBeingUsed).to.equal(true) + expect(exports.qTraversalComplete).to.equal(undefined) + events.emit(NO_BID, {type: NO_BID}) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue.push.calledTwice).to.equal(true) + clock.tick(1500) + expect(exports.qBeingUsed).to.equal(false) + expect(exports.qTraversalComplete).to.equal(true) + events.emit(BID_RESPONSE, {type: BID_RESPONSE}) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue.push.calledTwice).to.equal(true) + expect(exports.__atmtdAnalyticsQueue.push.calledThrice).to.equal(false) + expect(global.window.atmtdAnalytics.auctionInitHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.noBidHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.bidResponseHandler.calledOnce).to.equal(true) + }); + }); + describe('Process Events from Que when SDK still has not loaded', () => { before(() => { spec.enableAnalytics({ @@ -312,6 +397,8 @@ describe('Automatad Analytics Adapter', () => { sandbox.stub(exports.__atmtdAnalyticsQueue, 'push').callsFake((args) => { Array.prototype.push.apply(exports.__atmtdAnalyticsQueue, [args]); }) + exports.queuePointer = 0; + exports.retryCount = 0; }) beforeEach(() => { sandbox = sinon.createSandbox(); @@ -326,7 +413,6 @@ describe('Automatad Analytics Adapter', () => { sandbox.restore(); exports.queuePointer = 0; exports.retryCount = 0; - exports.__atmtdAnalyticsQueue = [] spec.disableAnalytics(); }) @@ -437,7 +523,6 @@ describe('Automatad Analytics Adapter', () => { } }); sandbox = sinon.createSandbox(); - sandbox.reset() const obj = { auctionInitHandler: (args) => {}, bidResponseHandler: (args) => {}, @@ -473,8 +558,10 @@ describe('Automatad Analytics Adapter', () => { ['impressionViewable', {type: 'impressionViewable'}] ] }); - after(() => { + afterEach(() => { sandbox.restore(); + }) + after(() => { spec.disableAnalytics(); }) diff --git a/test/spec/modules/azerionedgeRtdProvider_spec.js b/test/spec/modules/azerionedgeRtdProvider_spec.js new file mode 100644 index 00000000000..f08aaebdf55 --- /dev/null +++ b/test/spec/modules/azerionedgeRtdProvider_spec.js @@ -0,0 +1,183 @@ +import { config } from 'src/config.js'; +import * as azerionedgeRTD from 'modules/azerionedgeRtdProvider.js'; +import { loadExternalScript } from '../../../src/adloader.js'; + +describe('Azerion Edge RTD submodule', function () { + const STORAGE_KEY = 'ht-pa-v1-a'; + const USER_AUDIENCES = [ + { id: '1', visits: 123 }, + { id: '2', visits: 456 }, + ]; + + const key = 'publisher123'; + const bidders = ['appnexus', 'improvedigital']; + const process = { key: 'value' }; + const dataProvider = { name: 'azerionedge', waitForIt: true }; + + let reqBidsConfigObj; + let storageStub; + + beforeEach(function () { + config.resetConfig(); + reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + window.azerionPublisherAudiences = sinon.spy(); + storageStub = sinon.stub(azerionedgeRTD.storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + delete window.azerionPublisherAudiences; + storageStub.restore(); + }); + + describe('initialisation', function () { + let returned; + + beforeEach(function () { + returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider); + }); + + it('should return true', function () { + expect(returned).to.equal(true); + }); + + it('should load external script', function () { + expect(loadExternalScript.called).to.be.true; + }); + + it('should load external script with default versioned url', function () { + const expected = 'https://edge.hyth.io/js/v1/azerion-edge.min.js'; + expect(loadExternalScript.args[0][0]).to.deep.equal(expected); + }); + + it('should call azerionPublisherAudiencesStub with empty configuration', function () { + expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal({}); + }); + + describe('with key', function () { + beforeEach(function () { + window.azerionPublisherAudiences.resetHistory(); + loadExternalScript.resetHistory(); + returned = azerionedgeRTD.azerionedgeSubmodule.init({ + ...dataProvider, + params: { key }, + }); + }); + + it('should return true', function () { + expect(returned).to.equal(true); + }); + + it('should load external script with publisher id url', function () { + const expected = `https://edge.hyth.io/js/v1/${key}/azerion-edge.min.js`; + expect(loadExternalScript.args[0][0]).to.deep.equal(expected); + }); + }); + + describe('with process configuration', function () { + beforeEach(function () { + window.azerionPublisherAudiences.resetHistory(); + loadExternalScript.resetHistory(); + returned = azerionedgeRTD.azerionedgeSubmodule.init({ + ...dataProvider, + params: { process }, + }); + }); + + it('should return true', function () { + expect(returned).to.equal(true); + }); + + it('should call azerionPublisherAudiencesStub with process configuration', function () { + expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal( + process + ); + }); + }); + }); + + describe('gets audiences', function () { + let callbackStub; + + beforeEach(function () { + callbackStub = sinon.mock(); + }); + + describe('with empty storage', function () { + beforeEach(function () { + azerionedgeRTD.azerionedgeSubmodule.getBidRequestData( + reqBidsConfigObj, + callbackStub, + dataProvider + ); + }); + + it('does not run apply audiences to bidders', function () { + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({}); + }); + + it('calls callback anyway', function () { + expect(callbackStub.called).to.be.true; + }); + }); + + describe('with populate storage', function () { + beforeEach(function () { + storageStub + .withArgs(STORAGE_KEY) + .returns(JSON.stringify(USER_AUDIENCES)); + azerionedgeRTD.azerionedgeSubmodule.getBidRequestData( + reqBidsConfigObj, + callbackStub, + dataProvider + ); + }); + + it('does apply audiences to bidder', function () { + const segments = + reqBidsConfigObj.ortb2Fragments.bidder['improvedigital'].user.data[0] + .segment; + expect(segments).to.deep.equal([{ id: '1' }, { id: '2' }]); + }); + + it('calls callback always', function () { + expect(callbackStub.called).to.be.true; + }); + }); + }); + + describe('sets audiences in bidder', function () { + const audiences = USER_AUDIENCES.map(({ id }) => id); + const expected = { + user: { + data: [ + { + ext: { segtax: 4 }, + name: 'azerionedge', + segment: [{ id: '1' }, { id: '2' }], + }, + ], + }, + }; + + it('for improvedigital by default', function () { + azerionedgeRTD.setAudiencesToBidders( + reqBidsConfigObj, + dataProvider, + audiences + ); + expect( + reqBidsConfigObj.ortb2Fragments.bidder['improvedigital'] + ).to.deep.equal(expected); + }); + + bidders.forEach((bidder) => { + it(`for ${bidder}`, function () { + const config = { ...dataProvider, params: { bidders } }; + azerionedgeRTD.setAudiencesToBidders(reqBidsConfigObj, config, audiences); + expect(reqBidsConfigObj.ortb2Fragments.bidder[bidder]).to.deep.equal( + expected + ); + }); + }); + }); +}); diff --git a/test/spec/modules/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js index 79775f7b135..21f02b4f8ef 100644 --- a/test/spec/modules/cointrafficBidAdapter_spec.js +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -4,6 +4,11 @@ import { spec } from 'modules/cointrafficBidAdapter.js'; import { config } from 'src/config.js' import * as utils from 'src/utils.js' +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + */ + const ENDPOINT_URL = 'https://apps-pbd.ctraffic.io/pb/tmp'; describe('cointrafficBidAdapter', function () { diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index b8c872d879d..ebe1e9be4d4 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -255,13 +255,46 @@ describe('ColossussspAdapter', function () { }); describe('buildRequests with user ids', function () { - bid.userId = {} - bid.userId.britepoolid = 'britepoolid123'; - bid.userId.idl_env = 'idl_env123'; - bid.userId.tdid = 'tdid123'; - bid.userId.id5id = { uid: 'id5id123' }; - bid.userId.uid2 = { id: 'uid2id123' }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + var clonedBid = JSON.parse(JSON.stringify(bid)); + clonedBid.userId = {} + clonedBid.userId.britepoolid = 'britepoolid123'; + clonedBid.userId.idl_env = 'idl_env123'; + clonedBid.userId.tdid = 'tdid123'; + clonedBid.userId.id5id = { uid: 'id5id123' }; + clonedBid.userId.uid2 = { id: 'uid2id123' }; + clonedBid.userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '4679e98e-1d83-4718-8aba-aa88hhhaaa', + 'atype': 1 + } + ] + }, + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': 'e804908e-57b4-4f46-a097-08be44321e79', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + }, + { + 'source': 'neustar.biz', + 'uids': [ + { + 'id': 'E1:Bvss1x8hXM2zHeqiqj2umJUziavSvLT6E_ORri5fDCsZb-5sfD18oNWycTmdx6QBNdbURBVv466hLJiKSwHCaTxvROo8smjqj6GfvlKfzQI', + 'atype': 1 + } + ] + } + ]; + let serverRequest = spec.buildRequests([clonedBid], bidderRequest); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; let placements = data['placements']; @@ -270,11 +303,11 @@ describe('ColossussspAdapter', function () { let placement = placements[i]; expect(placement).to.have.property('eids') expect(placement.eids).to.be.an('array') - expect(placement.eids.length).to.be.equal(5) + expect(placement.eids.length).to.be.equal(8) for (let index in placement.eids) { let v = placement.eids[index]; expect(v).to.have.all.keys('source', 'uids') - expect(v.source).to.be.oneOf(['britepool.com', 'identityLink', 'adserver.org', 'id5-sync.com', 'uidapi.com']) + expect(v.source).to.be.oneOf(['pubcid.org', 'adserver.org', 'neustar.biz', 'britepool.com', 'identityLink', 'id5-sync.com', 'adserver.org', 'uidapi.com']) expect(v.uids).to.be.an('array'); expect(v.uids.length).to.be.equal(1) expect(v.uids[0]).to.have.property('id') diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index 78f6a9d410d..4d816c4e816 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -90,6 +90,10 @@ describe('connatixBidAdapter', function () { gdprApplies: true }, uspConsent: '1YYY', + gppConsent: { + gppString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + applicableSections: [7] + }, ortb2: { site: { data: { @@ -128,6 +132,7 @@ describe('connatixBidAdapter', function () { expect(serverRequest.data.refererInfo).to.equal(bidderRequest.refererInfo); expect(serverRequest.data.gdprConsent).to.equal(bidderRequest.gdprConsent); expect(serverRequest.data.uspConsent).to.equal(bidderRequest.uspConsent); + expect(serverRequest.data.gppConsent).to.equal(bidderRequest.gppConsent); expect(serverRequest.data.ortb2).to.equal(bidderRequest.ortb2); }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 1139dbf5210..1709acb465f 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2538,49 +2538,102 @@ describe('The Criteo bidding adapter', function () { }); it('should properly parse a bid response with FLEDGE auction configs', function () { + let auctionConfig1 = { + auctionSignals: {}, + decisionLogicUrl: 'https://grid-mercury.criteo.com/fledge/decision', + interestGroupBuyers: ['https://first-buyer-domain.com', 'https://second-buyer-domain.com'], + perBuyerSignals: { + 'https://first-buyer-domain.com': { + foo: 'bar', + }, + 'https://second-buyer-domain.com': { + foo: 'baz' + }, + }, + perBuyerTimeout: { + '*': 500, + 'buyer1': 100, + 'buyer2': 200 + }, + perBuyerGroupLimits: { + '*': 60, + 'buyer1': 300, + 'buyer2': 400 + }, + seller: 'https://seller-domain.com', + sellerTimeout: 500, + sellerSignals: { + foo: 'bar', + foo2: 'bar2', + floor: 1, + currency: 'USD', + perBuyerTimeout: { + 'buyer1': 100, + 'buyer2': 200 + }, + perBuyerGroupLimits: { + 'buyer1': 300, + 'buyer2': 400 + }, + }, + sellerCurrency: 'USD', + }; + let auctionConfig2 = { + auctionSignals: {}, + decisionLogicUrl: 'https://grid-mercury.criteo.com/fledge/decision', + interestGroupBuyers: ['https://first-buyer-domain.com', 'https://second-buyer-domain.com'], + perBuyerSignals: { + 'https://first-buyer-domain.com': { + foo: 'bar', + }, + 'https://second-buyer-domain.com': { + foo: 'baz' + }, + }, + perBuyerTimeout: { + '*': 500, + 'buyer1': 100, + 'buyer2': 200 + }, + perBuyerGroupLimits: { + '*': 60, + 'buyer1': 300, + 'buyer2': 400 + }, + seller: 'https://seller-domain.com', + sellerTimeout: 500, + sellerSignals: { + foo: 'bar', + floor: 1, + perBuyerTimeout: { + 'buyer1': 100, + 'buyer2': 200 + }, + perBuyerGroupLimits: { + 'buyer1': 300, + 'buyer2': 400 + }, + }, + sellerCurrency: '???' + }; const response = { body: { ext: { - igbid: [{ + igi: [{ impid: 'test-bidId', - igbuyer: [{ - origin: 'https://first-buyer-domain.com', - buyerdata: { - foo: 'bar', - }, - }, { - origin: 'https://second-buyer-domain.com', - buyerdata: { - foo: 'baz', - }, + igs: [{ + impid: 'test-bidId', + bidId: 'test-bidId', + config: auctionConfig1 }] }, { impid: 'test-bidId-2', - igbuyer: [{ - origin: 'https://first-buyer-domain.com', - buyerdata: { - foo: 'bar', - }, - }, { - origin: 'https://second-buyer-domain.com', - buyerdata: { - foo: 'baz', - }, + igs: [{ + impid: 'test-bidId-2', + bidId: 'test-bidId-2', + config: auctionConfig2 }] - }], - seller: 'https://seller-domain.com', - sellerTimeout: 500, - sellerSignals: { - foo: 'bar', - perBuyerTimeout: { 'buyer1': 100, 'buyer2': 200 }, - perBuyerGroupLimits: { 'buyer1': 300, 'buyer2': 400 }, - }, - sellerSignalsPerImp: { - 'test-bidId': { - foo2: 'bar2', - currency: 'USD' - }, - }, + }] }, }, }; @@ -2631,87 +2684,13 @@ describe('The Criteo bidding adapter', function () { expect(interpretedResponse.fledgeAuctionConfigs).to.have.lengthOf(2); expect(interpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal({ bidId: 'test-bidId', - config: { - auctionSignals: {}, - decisionLogicUrl: 'https://grid-mercury.criteo.com/fledge/decision', - interestGroupBuyers: ['https://first-buyer-domain.com', 'https://second-buyer-domain.com'], - perBuyerSignals: { - 'https://first-buyer-domain.com': { - foo: 'bar', - }, - 'https://second-buyer-domain.com': { - foo: 'baz' - }, - }, - perBuyerTimeout: { - '*': 50, - 'buyer1': 100, - 'buyer2': 200 - }, - perBuyerGroupLimits: { - '*': 60, - 'buyer1': 300, - 'buyer2': 400 - }, - seller: 'https://seller-domain.com', - sellerTimeout: 500, - sellerSignals: { - foo: 'bar', - foo2: 'bar2', - floor: 1, - currency: 'USD', - perBuyerTimeout: { - 'buyer1': 100, - 'buyer2': 200 - }, - perBuyerGroupLimits: { - 'buyer1': 300, - 'buyer2': 400 - }, - }, - sellerCurrency: 'USD', - }, + impid: 'test-bidId', + config: auctionConfig1, }); expect(interpretedResponse.fledgeAuctionConfigs[1]).to.deep.equal({ bidId: 'test-bidId-2', - config: { - auctionSignals: {}, - decisionLogicUrl: 'https://grid-mercury.criteo.com/fledge/decision', - interestGroupBuyers: ['https://first-buyer-domain.com', 'https://second-buyer-domain.com'], - perBuyerSignals: { - 'https://first-buyer-domain.com': { - foo: 'bar', - }, - 'https://second-buyer-domain.com': { - foo: 'baz' - }, - }, - perBuyerTimeout: { - '*': 50, - 'buyer1': 100, - 'buyer2': 200 - }, - perBuyerGroupLimits: { - '*': 60, - 'buyer1': 300, - 'buyer2': 400 - }, - seller: 'https://seller-domain.com', - sellerTimeout: 500, - sellerSignals: { - foo: 'bar', - floor: 1, - perBuyerTimeout: { - 'buyer1': 100, - 'buyer2': 200 - }, - perBuyerGroupLimits: { - 'buyer1': 300, - 'buyer2': 400 - }, - }, - sellerCurrency: '???' - }, + impid: 'test-bidId-2', + config: auctionConfig2, }); }); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index 8c7f0e84bce..ab99ba2aa0c 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -103,8 +103,8 @@ describe('bid interceptor', () => { }); describe('rule', () => { - function matchingRule({replace, options}) { - setRules({when: {}, then: replace, options: options}); + function matchingRule({replace, options, paapi}) { + setRules({when: {}, then: replace, options: options, paapi}); return interceptor.match({}); } @@ -164,6 +164,24 @@ describe('bid interceptor', () => { }); }); + describe('paapi', () => { + it('should accept literals', () => { + const mockConfig = [ + {paapi: 1}, + {paapi: 2} + ] + const paapi = matchingRule({paapi: mockConfig}).paapi({}); + expect(paapi).to.eql(mockConfig); + }); + + it('should accept a function and pass extra args to it', () => { + const paapiDef = sinon.stub(); + const args = [{}, {}, {}]; + matchingRule({paapi: paapiDef}).paapi(...args); + expect(paapiDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }) + }) + describe('.options', () => { it('should include default rule options', () => { const optDef = {someOption: 'value'}; @@ -181,16 +199,17 @@ describe('bid interceptor', () => { }); describe('intercept()', () => { - let done, addBid; + let done, addBid, addPaapiConfig; function intercept(args = {}) { const bidRequest = {bids: args.bids || []}; - return interceptor.intercept(Object.assign({bidRequest, done, addBid}, args)); + return interceptor.intercept(Object.assign({bidRequest, done, addBid, addPaapiConfig}, args)); } beforeEach(() => { done = sinon.spy(); addBid = sinon.spy(); + addPaapiConfig = sinon.spy(); }); describe('on no match', () => { @@ -253,6 +272,29 @@ describe('bid interceptor', () => { }); }); + it('should call addPaapiConfigs when provided', () => { + const mockPaapiConfigs = [ + {paapi: 1}, + {paapi: 2} + ] + setRules({ + when: {id: 2}, + paapi: mockPaapiConfigs, + }); + intercept({bidRequest: REQUEST}); + expect(addPaapiConfig.callCount).to.eql(2); + mockPaapiConfigs.forEach(cfg => sinon.assert.calledWith(addPaapiConfig, cfg)) + }) + + it('should not call onBid when then is null', () => { + setRules({ + when: {id: 2}, + then: null + }); + intercept({bidRequest: REQUEST}); + sinon.assert.notCalled(addBid); + }) + it('should call done()', () => { intercept({bidRequest: REQUEST}); expect(done.calledOnce).to.be.true; diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 961ccb33c4f..f1475ec3739 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -88,6 +88,22 @@ describe('discovery:BidAdapterTests', function () { bidderWinsCount: 0, }, ], + ortb2: { + user: { + data: { + segment: [ + { + id: '412' + } + ], + name: 'test.popin.cc', + ext: { + segclass: '1', + segtax: 503 + } + } + } + } }; let request = []; @@ -189,6 +205,13 @@ describe('discovery:BidAdapterTests', function () { let req_data = JSON.parse(request.data); expect(req_data.imp).to.have.lengthOf(1); }); + describe('first party data', function () { + it('should pass additional parameter in request for topics', function () { + const request = spec.buildRequests(bidRequestData.bids, bidRequestData); + let res = JSON.parse(request.data); + expect(res.ext.tpData).to.deep.equal(bidRequestData.ortb2.user.data); + }); + }); describe('discovery: buildRequests', function() { describe('getPmgUID function', function() { @@ -219,7 +242,7 @@ describe('discovery:BidAdapterTests', function () { storage.getCookie.callsFake(() => 'existing-uuid'); const uid = getPmgUID(); expect(uid).to.equal('existing-uuid'); - expect(storage.setCookie.called).to.be.false; + expect(storage.setCookie.called).to.be.true; }); it('should not set new UUID when cookies are not enabled', () => { diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index b27775bb887..e1f2394ab27 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -29,6 +29,18 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('unifiedId: ext generation with provider', function() { + const userId = { + tdid: {'id': 'some-sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{id: 'some-sample_id', atype: 1, ext: { rtiPartner: 'TDID', provider: 'some.provider.com' }}] + }); + }); + describe('id5Id', function() { it('does not include an ext if not provided', function() { const userId = { @@ -403,39 +415,6 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('thetradedesk', function() { - const userId = { - thetradedesk: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('thetradedesk with ext', function() { - const userId = { - thetradedesk: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - it('liveIntentId; getValue call and NO ext', function() { const userId = { lipb: { diff --git a/test/spec/modules/enrichmentFpdModule_spec.js b/test/spec/modules/enrichmentFpdModule_spec.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/spec/modules/hypelabBidAdapter_spec.js b/test/spec/modules/hypelabBidAdapter_spec.js index 4522073a2db..28d0739de79 100644 --- a/test/spec/modules/hypelabBidAdapter_spec.js +++ b/test/spec/modules/hypelabBidAdapter_spec.js @@ -92,7 +92,7 @@ const mockBidRequest = { placement_slug: 'test_placement', provider_version: '0.0.1', provider_name: 'prebid', - referrer: 'https://example.com', + location: 'https://example.com', sdk_version: '7.51.0-pre', sizes: [[728, 90]], wids: [], @@ -160,6 +160,9 @@ describe('hypelabBidAdapter', function () { expect(data.bidRequestsCount).to.be.a('number'); expect(data.bidderRequestsCount).to.be.a('number'); expect(data.bidderWinsCount).to.be.a('number'); + expect(data.dpr).to.be.a('number'); + expect(data.location).to.be.a('string'); + expect(data.floor).to.equal(null); }); describe('should set uuid to the first id in userIdAsEids', () => { @@ -211,6 +214,7 @@ describe('hypelabBidAdapter', function () { expect(data.ad).to.be.a('string'); expect(data.mediaType).to.be.a('string'); expect(data.meta.advertiserDomains).to.be.an('array'); + expect(data.meta.advertiserDomains[0]).to.be.a('string'); }); describe('should return a blank array if cpm is not set', () => { diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index ecce98d0b8d..af468f2fe4d 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1,5 +1,11 @@ import * as id5System from '../../../modules/id5IdSystem.js'; -import {coreStorage, getConsentHash, init, requestBidsHook, setSubmoduleRegistry} from '../../../modules/userId/index.js'; +import { + coreStorage, + getConsentHash, + init, + requestBidsHook, + setSubmoduleRegistry +} from '../../../modules/userId/index.js'; import {config} from '../../../src/config.js'; import * as events from '../../../src/events.js'; import CONSTANTS from '../../../src/constants.json'; @@ -10,7 +16,7 @@ import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {server} from '../../mocks/xhr.js'; import {expect} from 'chai'; -import { GreedyPromise } from '../../../src/utils/promise.js'; +import {GreedyPromise} from '../../../src/utils/promise.js'; const IdFetchFlow = id5System.IdFetchFlow; @@ -37,6 +43,22 @@ describe('ID5 ID System', function () { 'linkType': ID5_STORED_LINK_TYPE } }; + const EUID_STORED_ID = 'EUID_1'; + const EUID_SOURCE = 'uidapi.com'; + const ID5_STORED_OBJ_WITH_EUID = { + 'universal_uid': ID5_STORED_ID, + 'signature': ID5_STORED_SIGNATURE, + 'ext': { + 'linkType': ID5_STORED_LINK_TYPE, + 'euid': { + 'source': EUID_SOURCE, + 'uids': [{ + 'id': EUID_STORED_ID, + 'aType': 3 + }] + } + } + }; const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; const ID5_RESPONSE_LINK_TYPE = 2; @@ -886,6 +908,35 @@ describe('ID5 ID System', function () { }, {adUnits}); }); + it('should add stored EUID from cache to bids', function (done) { + id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_EUID), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.euid`); + expect(bid.userId.euid.uid).is.equal(EUID_STORED_ID); + expect(bid.userIdAsEids[0].uids[0].id).is.equal(ID5_STORED_ID); + expect(bid.userIdAsEids[1]).is.deep.equal({ + source: EUID_SOURCE, + uids: [{ + id: EUID_STORED_ID, + atype: 3, + ext: { + provider: ID5_SOURCE + } + }] + }) + }); + }); + done(); + }, {adUnits}); + }); + it('should add config value ID to bids', function (done) { init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); @@ -984,6 +1035,13 @@ describe('ID5 ID System', function () { it('should return undefined if passed a string', function () { expect(id5System.id5IdSubmodule.decode('somestring', getId5FetchConfig())).is.eq(undefined); }); + it('should decode euid from a stored object with EUID', function () { + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_EUID, getId5FetchConfig()).euid).is.deep.equal({ + 'source': EUID_SOURCE, + 'uid': EUID_STORED_ID, + 'ext': {'provider': ID5_SOURCE} + }); + }); }); describe('A/B Testing', function () { diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index fa7618814f8..5428fd0db0f 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -707,4 +707,67 @@ describe('jixie Adapter', function () { expect(jixieaux.ajax.calledWith(TRACKINGURL_)).to.equal(true); }) }); // describe + + describe('getUserSyncs', function () { + it('it should favour iframe over pixel if publisher allows iframe usersync', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true, + } + const response = { + 'userSyncs': [ + { + 'uf': 'https://syncstuff.jixie.io/', + 'up': 'https://syncstuff.jixie.io/image.gif' + }, + { + 'up': 'https://syncstuff.jixie.io/image1.gif' + } + ] + } + let result = spec.getUserSyncs(syncOptions, [{ body: response }]); + expect(result[0].type).to.equal('iframe') + expect(result[1].type).to.equal('image') + }) + + it('it should pick pixel if publisher not allow iframe', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true, + } + const response = { + 'userSyncs': [ + { + 'uf': 'https://syncstuff.jixie.io/', + 'up': 'https://syncstuff.jixie.io/image.gif' + }, + { + 'up': 'https://syncstuff.jixie.io/image1.gif' + } + ] + } + let result = spec.getUserSyncs(syncOptions, [{ body: response }]); + expect(result[0].type).to.equal('image') + expect(result[1].type).to.equal('image') + }) + + it('it should return nothing if pub only allow pixel but all usersyncs are iframe only', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true, + } + const response = { + 'userSyncs': [ + { + 'uf': 'https://syncstuff.jixie.io/', + }, + { + 'uf': 'https://syncstuff2.jixie.io/', + } + ] + } + let result = spec.getUserSyncs(syncOptions, [{ body: response }]); + expect(result.length).to.equal(0) + }) + }) }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 4638595e0d6..12fe9a7e800 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -237,6 +237,41 @@ describe('jwplayerRtdProvider', function() { fetchTargetingForMediaId(mediaIdWithSegment); + const bid = {}; + const adUnit = { + ortb2Imp: { + ext: { + data: { + jwTargeting: { + mediaID: mediaIdWithSegment, + playerDivId: validPlayerID + } + } + } + }, + bids: [ + bid + ] + }; + const expectedContentId = 'jw_' + mediaIdWithSegment; + const expectedTargeting = { + segments: validSegments, + content: { + id: expectedContentId + } + }; + jwplayerSubmodule.getBidRequestData({ adUnits: [adUnit] }, bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); + server.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + + it('includes backwards support for playerID when playerDivId is not set', function () { + const bidRequestSpy = sinon.spy(); + + fetchTargetingForMediaId(mediaIdWithSegment); + const bid = {}; const adUnit = { ortb2Imp: { @@ -605,23 +640,23 @@ describe('jwplayerRtdProvider', function() { it('should prioritize adUnit properties ', function () { const expectedMediaID = 'test_media_id'; const expectedPlayerID = 'test_player_id'; - const config = { playerID: 'bad_id', mediaID: 'bad_id' }; + const config = { playerDivId: 'bad_id', mediaID: 'bad_id' }; - const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerDivId: expectedPlayerID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); - expect(targeting).to.have.property('playerID', expectedPlayerID); + expect(targeting).to.have.property('playerDivId', expectedPlayerID); }); it('should use config properties as fallbacks', function () { const expectedMediaID = 'test_media_id'; const expectedPlayerID = 'test_player_id'; - const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; + const config = { playerDivId: expectedPlayerID, mediaID: 'bad_id' }; const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); - expect(targeting).to.have.property('playerID', expectedPlayerID); + expect(targeting).to.have.property('playerDivId', expectedPlayerID); }); it('should return undefined when Publisher Params are absent', function () { diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index f43c3b11aac..20a43d0397f 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -479,6 +479,21 @@ describe('kargo adapter tests', function () { floor: 1, fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' + }, + ext: { + ortb2Imp: { + ext: { + tid: '10101', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } } }, { @@ -491,6 +506,21 @@ describe('kargo adapter tests', function () { }, fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' + }, + floor: 2, + ext: { + ortb2Imp: { + ext: { + tid: '20202', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } } }, { @@ -503,6 +533,21 @@ describe('kargo adapter tests', function () { }, fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' + }, + floor: 3, + ext: { + ortb2Imp: { + ext: { + tid: '30303', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } } } ], @@ -553,6 +598,56 @@ describe('kargo adapter tests', function () { ] } ] + }, + ext: { + ortb2: { + device: { + sua: { + platform: { + brand: 'macOS', + version: ['12', '6', '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: 1, + model: 'model', + source: 1, + } + }, + site: { + id: '1234', + name: 'SiteName', + cat: ['IAB1', 'IAB2', 'IAB3'] + }, + user: { + data: [ + { + name: 'prebid.org', + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + { + id: '133' + }, + ] + }, + ] + } + } } }; @@ -605,6 +700,16 @@ describe('kargo adapter tests', function () { payload['gdprConsent'] = gdpr } + clonedBids.forEach(bid => { + if (bid.mediaTypes.banner) { + bid.getFloor = () => ({ currency: 'USD', floor: 1 }); + } else if (bid.mediaTypes.video) { + bid.getFloor = () => ({ currency: 'USD', floor: 2 }); + } else if (bid.mediaTypes.native) { + bid.getFloor = () => ({ currency: 'USD', floor: 3 }); + } + }); + var request = spec.buildRequests(clonedBids, payload); var krakenParams = request.data; @@ -725,7 +830,8 @@ describe('kargo adapter tests', function () { adm: '
', width: 320, height: 50, - metadata: {} + metadata: {}, + creativeID: 'bar' }, 2: { id: 'bar', @@ -736,14 +842,16 @@ describe('kargo adapter tests', function () { targetingCustom: 'dmpmptest1234', metadata: { landingPageDomain: ['https://foobar.com'] - } + }, + creativeID: 'foo' }, 3: { id: 'bar', cpm: 2.5, adm: '
', width: 300, - height: 250 + height: 250, + creativeID: 'foo' }, 4: { id: 'bar', @@ -753,6 +861,7 @@ describe('kargo adapter tests', function () { height: 250, mediaType: 'banner', metadata: {}, + creativeID: 'foo', currency: 'EUR' }, 5: { @@ -763,6 +872,7 @@ describe('kargo adapter tests', function () { height: 250, mediaType: 'video', metadata: {}, + creativeID: 'foo', currency: 'EUR' }, 6: { @@ -774,6 +884,7 @@ describe('kargo adapter tests', function () { height: 250, mediaType: 'video', metadata: {}, + creativeID: 'foo', currency: 'EUR' } } @@ -818,7 +929,7 @@ describe('kargo adapter tests', function () { width: 320, height: 50, ttl: 300, - creativeId: 'foo', + creativeId: 'bar', dealId: undefined, netRevenue: true, currency: 'USD', @@ -833,7 +944,7 @@ describe('kargo adapter tests', function () { width: 300, height: 250, ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: 'dmpmptest1234', netRevenue: true, currency: 'USD', @@ -850,7 +961,7 @@ describe('kargo adapter tests', function () { width: 300, height: 250, ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: undefined, netRevenue: true, currency: 'USD', @@ -865,7 +976,7 @@ describe('kargo adapter tests', function () { width: 300, height: 250, ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: undefined, netRevenue: true, currency: 'EUR', @@ -880,7 +991,7 @@ describe('kargo adapter tests', function () { height: 250, vastXml: '', ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: undefined, netRevenue: true, currency: 'EUR', @@ -895,7 +1006,7 @@ describe('kargo adapter tests', function () { height: 250, vastUrl: 'https://foobar.com/vast_adm', ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: undefined, netRevenue: true, currency: 'EUR', diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index 6002e827593..e280d9108a0 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -294,7 +294,7 @@ describe('LiveIntentMinimalId', function() { const provider = 'liveintent.com' refererInfoStub.returns({domain: provider}) const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }); - expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'thetradedesk': 'bar'}, 'thetradedesk': {'id': 'bar', 'ext': {'provider': provider}}}); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); it('should allow disabling nonId resolution', function() { diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 23f7bcda36b..c6108b49715 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -432,7 +432,7 @@ describe('LiveIntentId', function() { const provider = 'liveintent.com' refererInfoStub.returns({domain: provider}) const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }); - expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'thetradedesk': 'bar'}, 'thetradedesk': {'id': 'bar', 'ext': {'provider': provider}}}); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); it('should allow disabling nonId resolution', function() { diff --git a/test/spec/modules/luceadBidAdapter_spec.js b/test/spec/modules/luceadBidAdapter_spec.js index fa8d76cc30b..72bc7cc2d6e 100644 --- a/test/spec/modules/luceadBidAdapter_spec.js +++ b/test/spec/modules/luceadBidAdapter_spec.js @@ -15,6 +15,12 @@ describe('Lucead Adapter', () => { }); }); + describe('utils functions', function () { + it('returns false', function () { + expect(spec.isDevEnv()).to.be.false; + }); + }); + describe('isBidRequestValid', function () { let bid; beforeEach(function () { @@ -33,7 +39,7 @@ describe('Lucead Adapter', () => { describe('onBidWon', function () { let sandbox; - const bid = { foo: 'bar' }; + const bid = { foo: 'bar', creativeId: 'ssp:improve' }; beforeEach(function () { sandbox = sinon.sandbox.create(); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 0dfd6c15ba8..397ee4a8577 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -1741,6 +1741,79 @@ describe('magnite analytics adapter', function () { expect(message1.bidsWon).to.deep.equal([expectedMessage1]); }); }); + describe('cookieless', () => { + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + cookieles: undefined + } + }); + }) + afterEach(() => { + magniteAdapter.disableAnalytics(); + }) + it('should add sufix _cookieless to the wrapper.rule if ortb2.device.ext.cdep start with "treatment" or "control_2"', () => { + // Set the confs + config.setConfig({ + rubicon: { + wrapperName: '1001_general', + wrapperFamily: 'general', + rule_name: 'desktop-magnite.com', + } + }); + const auctionId = MOCK.AUCTION_INIT.auctionId; + + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.bidderRequests[0].ortb2.device.ext = { cdep: 'treatment' }; + // Run auction + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + [gptSlotRenderEnded0].forEach(gptEvent => mockGpt.emitEvent(gptEvent.eventName, gptEvent.params)); + events.emit(BID_WON, { ...MOCK.BID_WON, auctionId }); + clock.tick(rubiConf.analyticsEventDelay); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + expect(message.wrapper).to.deep.equal({ + name: '1001_general', + family: 'general', + rule: 'desktop-magnite.com_cookieless', + }); + }) + it('should add cookieless to the wrapper.rule if ortb2.device.ext.cdep start with "treatment" or "control_2"', () => { + // Set the confs + config.setConfig({ + rubicon: { + wrapperName: '1001_general', + wrapperFamily: 'general', + } + }); + const auctionId = MOCK.AUCTION_INIT.auctionId; + + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.bidderRequests[0].ortb2.device.ext = { cdep: 'control_2' }; + // Run auction + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + [gptSlotRenderEnded0].forEach(gptEvent => mockGpt.emitEvent(gptEvent.eventName, gptEvent.params)); + events.emit(BID_WON, { ...MOCK.BID_WON, auctionId }); + clock.tick(rubiConf.analyticsEventDelay); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + expect(message.wrapper).to.deep.equal({ + family: 'general', + name: '1001_general', + rule: 'cookieless', + }); + }); + }); }); describe('billing events integration', () => { diff --git a/test/spec/modules/mediaimpactBidAdapter_spec.js b/test/spec/modules/mediaimpactBidAdapter_spec.js new file mode 100644 index 00000000000..3d706e59c3f --- /dev/null +++ b/test/spec/modules/mediaimpactBidAdapter_spec.js @@ -0,0 +1,336 @@ +import {expect} from 'chai'; +import {spec, ENDPOINT_PROTOCOL, ENDPOINT_DOMAIN, ENDPOINT_PATH} from 'modules/mediaimpactBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'mediaimpact'; + +describe('MediaimpactAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + let validRequest = { + 'params': { + 'unitId': 123 + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(true); + }); + + it('should return true when required params is srting', function () { + let validRequest = { + 'params': { + 'unitId': '456' + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let validRequest = { + 'params': { + 'unknownId': 123 + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when required params is 0', function () { + let validRequest = { + 'params': { + 'unitId': 0 + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let validEndpoint = ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&partner=777&sizes=300x250|300x600,728x90,300x250&referer=https%3A%2F%2Ftest.domain'; + + let validRequest = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'unitId': 123 + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e' + }, + { + 'bidder': BIDDER_CODE, + 'params': { + 'unitId': '456' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '22aidtbx5eabd9' + }, + { + 'bidder': BIDDER_CODE, + 'params': { + 'partnerId': 777 + }, + 'adUnitCode': 'partner-code-3', + 'sizes': [[300, 250]], + 'bidId': '5d4531d5a6c013' + } + ]; + + let bidderRequest = { + refererInfo: { + page: 'https://test.domain' + } + }; + + it('bidRequest HTTP method', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.method).to.equal('POST'); + }); + + it('bidRequest url', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(validEndpoint); + }); + + it('bidRequest data', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload[0].unitId).to.equal(123); + expect(payload[0].sizes).to.deep.equal([[300, 250], [300, 600]]); + expect(payload[0].bidId).to.equal('30b31c1838de1e'); + expect(payload[1].unitId).to.equal(456); + expect(payload[1].sizes).to.deep.equal([[728, 90]]); + expect(payload[1].bidId).to.equal('22aidtbx5eabd9'); + expect(payload[2].partnerId).to.equal(777); + expect(payload[2].sizes).to.deep.equal([[300, 250]]); + expect(payload[2].bidId).to.equal('5d4531d5a6c013'); + }); + }); + + describe('joinSizesToString', function () { + it('success convert sizes list to string', function () { + const sizesStr = spec.joinSizesToString([[300, 250], [300, 600]]); + expect(sizesStr).to.equal('300x250|300x600'); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = { + 'method': 'POST', + 'url': ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + ENDPOINT_PATH + '?tag=123,456&partner=777code=adunit-code-1,adunit-code-2,partner-code-3&bid=30b31c1838de1e,22aidtbx5eabd9,5d4531d5a6c013&sizes=300x250|300x600,728x90,300x250&referer=https%3A%2F%2Ftest.domain', + 'data': '[{"unitId": 13144370,"adUnitCode": "div-gpt-ad-1460505748561-0","sizes": [[300, 250], [300, 600]],"bidId": "2bdcb0b203c17d","referer": "https://test.domain/index.html"},' + + '{"unitId": 13144370,"adUnitCode":"div-gpt-ad-1460505748561-1","sizes": [[768, 90]],"bidId": "3dc6b8084f91a8","referer": "https://test.domain/index.html"},' + + '{"unitId": 0,"partnerId": 777,"adUnitCode":"div-gpt-ad-1460505748561-2","sizes": [[300, 250]],"bidId": "5d4531d5a6c013","referer": "https://test.domain/index.html"}]' + }; + + const bidResponse = { + body: { + 'div-gpt-ad-1460505748561-0': + { + 'ad': '
ad
', + 'width': 300, + 'height': 250, + 'creativeId': '8:123456', + 'adomain': [ + 'test.domain' + ], + 'syncs': [ + {'type': 'image', 'url': 'https://test.domain/tracker_1.gif'}, + {'type': 'image', 'url': 'https://test.domain/tracker_2.gif'}, + {'type': 'image', 'url': 'https://test.domain/tracker_3.gif'} + ], + 'winNotification': [ + { + 'method': 'POST', + 'path': '/hb/bid_won?test=1', + 'data': { + 'ad': [ + {'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'} + ], + 'unit_id': 1234, + 'site_id': 123 + } + } + ], + 'cpm': 0.01, + 'currency': 'USD', + 'netRevenue': true + } + }, + headers: {} + }; + + it('result is correct', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0].requestId).to.equal('2bdcb0b203c17d'); + expect(result[0].cpm).to.equal(0.01); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('8:123456'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].ttl).to.equal(60); + expect(result[0].meta.advertiserDomains).to.deep.equal(['test.domain']); + expect(result[0].winNotification[0]).to.deep.equal({'method': 'POST', 'path': '/hb/bid_won?test=1', 'data': {'ad': [{'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'}], 'unit_id': 1234, 'site_id': 123}}); + }); + }); + + describe('adResponse', function () { + const bid = { + 'unitId': 13144370, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2bdcb0b203c17d', + 'referer': 'https://test.domain/index.html' + }; + const ad = { + 'ad': '
ad
', + 'width': 300, + 'height': 250, + 'creativeId': '8:123456', + 'syncs': [], + 'winNotification': [], + 'cpm': 0.01, + 'currency': 'USD', + 'netRevenue': true, + 'adomain': [ + 'test.domain' + ], + }; + + it('fill ad for response', function () { + const result = spec.adResponse(bid, ad); + expect(result.requestId).to.equal('2bdcb0b203c17d'); + expect(result.cpm).to.equal(0.01); + expect(result.width).to.equal(300); + expect(result.height).to.equal(250); + expect(result.creativeId).to.equal('8:123456'); + expect(result.currency).to.equal('USD'); + expect(result.ttl).to.equal(60); + expect(result.meta.advertiserDomains).to.deep.equal(['test.domain']); + }); + }); + + describe('onBidWon', function () { + const bid = { + winNotification: [ + { + 'method': 'POST', + 'path': '/hb/bid_won?test=1', + 'data': { + 'ad': [ + {'dsp': 8, 'id': 800008, 'cost': 0.01, 'nurl': 'http://test.domain/'} + ], + 'unit_id': 1234, + 'site_id': 123 + } + } + ] + }; + + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'postRequest') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('calls mediaimpact callback endpoint', () => { + const result = spec.onBidWon(bid); + expect(result).to.equal(true); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(ENDPOINT_PROTOCOL + '://' + ENDPOINT_DOMAIN + '/hb/bid_won?test=1'); + expect(ajaxStub.firstCall.args[1]).to.deep.equal(JSON.stringify(bid.winNotification[0].data)); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse = [{ + body: { + 'div-gpt-ad-1460505748561-0': + { + 'ad': '
ad
', + 'width': 300, + 'height': 250, + 'creativeId': '8:123456', + 'adomain': [ + 'test.domain' + ], + 'syncs': [ + {'type': 'image', 'link': 'https://test.domain/tracker_1.gif'}, + {'type': 'image', 'link': 'https://test.domain/tracker_2.gif'}, + {'type': 'image', 'link': 'https://test.domain/tracker_3.gif'} + ], + 'winNotification': [ + { + 'method': 'POST', + 'path': '/hb/bid_won?test=1', + 'data': { + 'ad': [ + {'dsp': 8, 'id': 800008, 'cost': 1.0e-5, 'nurl': 'https://test.domain/'} + ], + 'unit_id': 1234, + 'site_id': 123 + } + } + ], + 'cpm': 0.01, + 'currency': 'USD', + 'netRevenue': true + } + }, + headers: {} + }]; + + it('should return nothing when sync is disabled', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': false + }; + + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs).to.deep.equal([]); + }); + + it('should register image sync when only image is enabled where gdprConsent is undefined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + }; + + const gdprConsent = undefined; + let syncs = spec.getUserSyncs(syncOptions, bidResponse, gdprConsent); + expect(syncs.length).to.equal(3); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://test.domain/tracker_1.gif'); + }); + + it('should register image sync when only image is enabled where gdprConsent is defined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'someString', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + + let syncs = spec.getUserSyncs(syncOptions, bidResponse, gdprConsent); + expect(syncs.length).to.equal(3); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://test.domain/tracker_1.gif?gdpr=1&gdpr_consent=someString'); + }); + }); +}); diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index b9871bbbe71..ff58671b17b 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -34,7 +34,7 @@ describe('nextMillenniumBidAdapterTests', () => { bidfloorcur: 'EUR', bidfloor: 1.11, ext: {prebid: {storedrequest: {id: '123'}}}, - banner: {format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + banner: {w: 300, h: 250, format: [{w: 300, h: 250}, {w: 320, h: 250}]}, }, }, @@ -43,13 +43,13 @@ describe('nextMillenniumBidAdapterTests', () => { data: { id: '234', bid: { - mediaTypes: {video: {playerSize: [400, 300]}}, + mediaTypes: {video: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}}, adUnitCode: 'test-video-1', }, mediaTypes: { video: { - data: {playerSize: [400, 300]}, + data: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}, bidfloorcur: 'USD', }, }, @@ -59,7 +59,39 @@ describe('nextMillenniumBidAdapterTests', () => { id: 'test-video-1', bidfloorcur: 'USD', ext: {prebid: {storedrequest: {id: '234'}}}, - video: {w: 400, h: 300}, + video: { + mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript'], + api: [2], + placement: 1, + plcmt: 1, + w: 400, + h: 300, + }, + }, + }, + + { + title: 'imp - mediaTypes.video is empty', + data: { + id: '234', + bid: { + mediaTypes: {video: {w: 640, h: 480}}, + adUnitCode: 'test-video-2', + }, + + mediaTypes: { + video: { + data: {w: 640, h: 480}, + bidfloorcur: 'USD', + }, + }, + }, + + expected: { + id: 'test-video-2', + bidfloorcur: 'USD', + ext: {prebid: {storedrequest: {id: '234'}}}, + video: {w: 640, h: 480, mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript']}, }, }, ]; @@ -902,7 +934,7 @@ describe('nextMillenniumBidAdapterTests', () => { ]; for (let {eventName, bid, expected} of dataForTests) { - const url = spec.getUrlPixelMetric(eventName, bid); + const url = spec._getUrlPixelMetric(eventName, bid); expect(url).to.equal(expected); }; }) diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 7091bb56631..f18e0365226 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -20,12 +20,12 @@ const instreamResponse = { 'crid': '97517771', 'h': 1, 'w': 1, + 'adm': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ', 'ext': { 'mediaType': 'instream', 'ssp': 'appnexus', 'divId': 'video1', 'adUnitCode': 'video1', - 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' } } ], @@ -374,7 +374,7 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); - expect(requestContent.ext.bidderVersion).to.be.eql('3.0'); + expect(requestContent.ext.bidderVersion).to.be.eql('4.0'); expect(requestContent.ext.source).to.be.eql('prebid.js'); }); @@ -561,7 +561,7 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].adm); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); @@ -585,11 +585,11 @@ describe('Nexx360 bid adapter tests', function () { 'crid': '97517771', 'h': 1, 'w': 1, + 'adm': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ', 'ext': { 'mediaType': 'outstream', 'ssp': 'appnexus', 'adUnitCode': 'div-1', - 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' } } ], @@ -602,7 +602,7 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].adm); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); expect(typeof output[0].renderer).to.be.eql('object'); diff --git a/test/spec/modules/nobidAnalyticsAdapter_spec.js b/test/spec/modules/nobidAnalyticsAdapter_spec.js index 3da334eea97..f6c741bb7ff 100644 --- a/test/spec/modules/nobidAnalyticsAdapter_spec.js +++ b/test/spec/modules/nobidAnalyticsAdapter_spec.js @@ -127,9 +127,9 @@ describe('NoBid Prebid Analytic', function () { mediaType: 'banner', source: 'client', cpm: 6.4, + currency: 'EUR', creativeId: 'TEST', dealId: '', - currency: 'USD', netRevenue: true, ttl: 300, ad: 'AD HERE', @@ -167,13 +167,17 @@ describe('NoBid Prebid Analytic', function () { ] }; - const requestOutgoing = { + const expectedOutgoingRequest = { + version: nobidAnalyticsVersion, bidderCode: 'nobid', statusMessage: 'Bid available', adId: '106d14b7d06b607', requestId: '67a7f0e7ea55c4', mediaType: 'banner', cpm: 6.4, + currency: 'EUR', + originalCpm: 6.44, + originalCurrency: 'USD', adUnitCode: 'leaderboard', timeToRespond: 545, size: '728x90', @@ -197,16 +201,20 @@ describe('NoBid Prebid Analytic', function () { clock.tick(5000); expect(server.requests).to.have.length(1); const bidWonRequest = JSON.parse(server.requests[0].requestBody); - expect(bidWonRequest).to.have.property('bidderCode', requestOutgoing.bidderCode); - expect(bidWonRequest).to.have.property('statusMessage', requestOutgoing.statusMessage); - expect(bidWonRequest).to.have.property('adId', requestOutgoing.adId); - expect(bidWonRequest).to.have.property('requestId', requestOutgoing.requestId); - expect(bidWonRequest).to.have.property('mediaType', requestOutgoing.mediaType); - expect(bidWonRequest).to.have.property('cpm', requestOutgoing.cpm); - expect(bidWonRequest).to.have.property('adUnitCode', requestOutgoing.adUnitCode); - expect(bidWonRequest).to.have.property('timeToRespond', requestOutgoing.timeToRespond); - expect(bidWonRequest).to.have.property('size', requestOutgoing.size); - expect(bidWonRequest).to.have.property('topLocation', requestOutgoing.topLocation); + expect(bidWonRequest).to.have.property('version', nobidAnalyticsVersion); + expect(bidWonRequest).to.have.property('bidderCode', expectedOutgoingRequest.bidderCode); + expect(bidWonRequest).to.have.property('statusMessage', expectedOutgoingRequest.statusMessage); + expect(bidWonRequest).to.have.property('adId', expectedOutgoingRequest.adId); + expect(bidWonRequest).to.have.property('requestId', expectedOutgoingRequest.requestId); + expect(bidWonRequest).to.have.property('mediaType', expectedOutgoingRequest.mediaType); + expect(bidWonRequest).to.have.property('cpm', expectedOutgoingRequest.cpm); + expect(bidWonRequest).to.have.property('currency', expectedOutgoingRequest.currency); + expect(bidWonRequest).to.have.property('originalCpm', expectedOutgoingRequest.originalCpm); + expect(bidWonRequest).to.have.property('originalCurrency', expectedOutgoingRequest.originalCurrency); + expect(bidWonRequest).to.have.property('adUnitCode', expectedOutgoingRequest.adUnitCode); + expect(bidWonRequest).to.have.property('timeToRespond', expectedOutgoingRequest.timeToRespond); + expect(bidWonRequest).to.have.property('size', expectedOutgoingRequest.size); + expect(bidWonRequest).to.have.property('topLocation', expectedOutgoingRequest.topLocation); expect(bidWonRequest).to.not.have.property('pbCg'); done(); @@ -304,10 +312,10 @@ describe('NoBid Prebid Analytic', function () { auctionId: '4c056b3c-f1a6-46bd-8d82-58c15b22fcfa', mediaType: 'banner', source: 'client', - cpm: 6.44, + cpm: 5.93, + currency: 'EUR', creativeId: 'TEST', dealId: '', - currency: 'USD', netRevenue: true, ttl: 300, ad: '', @@ -336,7 +344,7 @@ describe('NoBid Prebid Analytic', function () { timeout: 3000 }; - const requestOutgoing = { + const expectedOutgoingRequest = { auctionId: '4c056b3c-f1a6-46bd-8d82-58c15b22fcfa', bidderRequests: [ { @@ -364,7 +372,10 @@ describe('NoBid Prebid Analytic', function () { width: 728, height: 90, mediaType: 'banner', - cpm: 6.44, + cpm: 5.93, + currency: 'EUR', + originalCpm: 6.44, + originalCurrency: 'USD', adUnitCode: 'leaderboard' } ] @@ -387,22 +398,26 @@ describe('NoBid Prebid Analytic', function () { clock.tick(5000); expect(server.requests).to.have.length(1); const auctionEndRequest = JSON.parse(server.requests[0].requestBody); - expect(auctionEndRequest).to.have.property('auctionId', requestOutgoing.auctionId); + expect(auctionEndRequest).to.have.property('version', nobidAnalyticsVersion); + expect(auctionEndRequest).to.have.property('auctionId', expectedOutgoingRequest.auctionId); expect(auctionEndRequest.bidderRequests).to.have.length(1); - expect(auctionEndRequest.bidderRequests[0].bidderCode).to.equal(requestOutgoing.bidderRequests[0].bidderCode); + expect(auctionEndRequest.bidderRequests[0].bidderCode).to.equal(expectedOutgoingRequest.bidderRequests[0].bidderCode); expect(auctionEndRequest.bidderRequests[0].bids).to.have.length(1); expect(typeof auctionEndRequest.bidderRequests[0].bids[0].bidder).to.equal('undefined'); - expect(auctionEndRequest.bidderRequests[0].bids[0].adUnitCode).to.equal(requestOutgoing.bidderRequests[0].bids[0].adUnitCode); + expect(auctionEndRequest.bidderRequests[0].bids[0].adUnitCode).to.equal(expectedOutgoingRequest.bidderRequests[0].bids[0].adUnitCode); expect(typeof auctionEndRequest.bidderRequests[0].bids[0].params).to.equal('undefined'); expect(typeof auctionEndRequest.bidderRequests[0].bids[0].src).to.equal('undefined'); - expect(auctionEndRequest.bidderRequests[0].refererInfo.topmostLocation).to.equal(requestOutgoing.bidderRequests[0].refererInfo.topmostLocation); + expect(auctionEndRequest.bidderRequests[0].refererInfo.topmostLocation).to.equal(expectedOutgoingRequest.bidderRequests[0].refererInfo.topmostLocation); expect(auctionEndRequest.bidsReceived).to.have.length(1); - expect(auctionEndRequest.bidsReceived[0].bidderCode).to.equal(requestOutgoing.bidsReceived[0].bidderCode); - expect(auctionEndRequest.bidsReceived[0].width).to.equal(requestOutgoing.bidsReceived[0].width); - expect(auctionEndRequest.bidsReceived[0].height).to.equal(requestOutgoing.bidsReceived[0].height); - expect(auctionEndRequest.bidsReceived[0].mediaType).to.equal(requestOutgoing.bidsReceived[0].mediaType); - expect(auctionEndRequest.bidsReceived[0].cpm).to.equal(requestOutgoing.bidsReceived[0].cpm); - expect(auctionEndRequest.bidsReceived[0].adUnitCode).to.equal(requestOutgoing.bidsReceived[0].adUnitCode); + expect(auctionEndRequest.bidsReceived[0].bidderCode).to.equal(expectedOutgoingRequest.bidsReceived[0].bidderCode); + expect(auctionEndRequest.bidsReceived[0].width).to.equal(expectedOutgoingRequest.bidsReceived[0].width); + expect(auctionEndRequest.bidsReceived[0].height).to.equal(expectedOutgoingRequest.bidsReceived[0].height); + expect(auctionEndRequest.bidsReceived[0].mediaType).to.equal(expectedOutgoingRequest.bidsReceived[0].mediaType); + expect(auctionEndRequest.bidsReceived[0].cpm).to.equal(expectedOutgoingRequest.bidsReceived[0].cpm); + expect(auctionEndRequest.bidsReceived[0].currency).to.equal(expectedOutgoingRequest.bidsReceived[0].currency); + expect(auctionEndRequest.bidsReceived[0].originalCpm).to.equal(expectedOutgoingRequest.bidsReceived[0].originalCpm); + expect(auctionEndRequest.bidsReceived[0].originalCurrency).to.equal(expectedOutgoingRequest.bidsReceived[0].originalCurrency); + expect(auctionEndRequest.bidsReceived[0].adUnitCode).to.equal(expectedOutgoingRequest.bidsReceived[0].adUnitCode); expect(typeof auctionEndRequest.bidsReceived[0].source).to.equal('undefined'); done(); diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index a7b7ba09113..10a9c4c946c 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -247,6 +247,28 @@ describe('omsBidAdapter', function () { expect(data.user.ext.ids).is.deep.equal(userId); }); + it('sends gpid parameters', function () { + bidRequests[0].ortb2Imp = { + 'ext': { + 'gpid': '/1111/home-left', + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/1111/home' + }, + 'pbadslot': '/1111/home-left' + } + } + } + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.imp[0].ext).to.not.be.undefined; + expect(data.imp[0].ext.gpid).to.not.be.undefined; + expect(data.imp[0].ext.adserverName).to.not.be.undefined; + expect(data.imp[0].ext.adslot).to.not.be.undefined; + expect(data.imp[0].ext.pbadslot).to.not.be.undefined; + }); + context('when element is fully in view', function () { it('returns 100', function () { Object.assign(element, {width: 600, height: 400}); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index c3d8a4ee0e1..93db5ffc57f 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -273,6 +273,7 @@ describe('onetag', function () { }); it('should send GDPR consent data', function () { let consentString = 'consentString'; + let addtlConsent = '2~1.35.41.101~dv.9.21.81'; let bidderRequest = { 'bidderCode': 'onetag', 'auctionId': '1d1a030790a475', @@ -280,7 +281,8 @@ describe('onetag', function () { 'timeout': 3000, 'gdprConsent': { consentString: consentString, - gdprApplies: true + gdprApplies: true, + addtlConsent: addtlConsent } }; let serverRequest = spec.buildRequests([bannerBid], bidderRequest); @@ -289,6 +291,7 @@ describe('onetag', function () { expect(payload).to.exist; expect(payload.gdprConsent).to.exist; expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.gdprConsent.addtlConsent).to.exist.and.to.equal(addtlConsent); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; }); it('Should send GPP consent data', function () { diff --git a/test/spec/modules/opscoBidAdapter_spec.js b/test/spec/modules/opscoBidAdapter_spec.js new file mode 100644 index 00000000000..38cacff8f82 --- /dev/null +++ b/test/spec/modules/opscoBidAdapter_spec.js @@ -0,0 +1,260 @@ +import {expect} from 'chai'; +import {spec} from 'modules/opscoBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('opscoBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + const validBid = { + bidder: 'opsco', + params: { + placementId: '123', + publisherId: '456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + it('should return true when required params are present', function () { + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should return false when placementId is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.params.placementId; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when publisherId is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.params.publisherId; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when mediaTypes.banner.sizes is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.mediaTypes.banner.sizes; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when mediaTypes.banner is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.mediaTypes.banner; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when bid params are missing', function () { + const invalidBid = {bidder: 'opsco'}; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when bid params are empty', function () { + const invalidBid = {bidder: 'opsco', params: {}}; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let validBid, bidderRequest; + + beforeEach(function () { + validBid = { + bidder: 'opsco', + params: { + placementId: '123', + publisherId: '456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + bidderRequest = { + bidderRequestId: 'bid123', + refererInfo: { + domain: 'example.com', + page: 'https://example.com/page', + ref: 'https://referrer.com' + }, + gdprConsent: { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true + }, + }; + }); + + it('should return true when banner sizes are defined', function () { + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should return false when banner sizes are invalid', function () { + const invalidSizes = [ + '2:1', + undefined, + 123, + 'undefined' + ]; + + invalidSizes.forEach((sizes) => { + validBid.mediaTypes.banner.sizes = sizes; + expect(spec.isBidRequestValid(validBid)).to.be.false; + }); + }); + + it('should send GDPR consent in the payload if present', function () { + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).user.ext.consent).to.deep.equal('GDPR_CONSENT_STRING'); + }); + + it('should send CCPA in the payload if present', function () { + const ccpa = '1YYY'; + bidderRequest.uspConsent = ccpa; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).regs.ext.us_privacy).to.equal(ccpa); + }); + + it('should send eids in the payload if present', function () { + const eids = {data: [{source: 'test', uids: [{id: '123', ext: {}}]}]}; + validBid.userIdAsEids = eids; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).user.ext.eids).to.deep.equal(eids); + }); + + it('should send schain in the payload if present', function () { + const schain = {'ver': '1.0', 'complete': 1, 'nodes': [{'asi': 'exchange1.com', 'sid': '1234', 'hp': 1}]}; + validBid.schain = schain; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).source.ext.schain).to.deep.equal(schain); + }); + + it('should correctly identify test mode', function () { + validBid.params.test = true; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).test).to.equal(1); + }); + }); + + describe('interpretResponse', function () { + const validResponse = { + body: { + seatbid: [ + { + bid: [ + { + impid: 'bid1', + price: 1.5, + w: 300, + h: 250, + crid: 'creative1', + currency: 'USD', + netRevenue: true, + ttl: 300, + adm: '
Ad content
', + mtype: 1 + }, + { + impid: 'bid2', + price: 2.0, + w: 728, + h: 90, + crid: 'creative2', + currency: 'USD', + netRevenue: true, + ttl: 300, + adm: '
Ad content
', + mtype: 1 + } + ] + } + ] + } + }; + + const emptyResponse = { + body: { + seatbid: [] + } + }; + + it('should return an array of bid objects with valid response', function () { + const interpretedBids = spec.interpretResponse(validResponse); + const expectedBids = validResponse.body.seatbid[0].bid; + expect(interpretedBids).to.have.lengthOf(expectedBids.length); + expectedBids.forEach((expectedBid, index) => { + expect(interpretedBids[index]).to.have.property('requestId', expectedBid.impid); + expect(interpretedBids[index]).to.have.property('cpm', expectedBid.price); + expect(interpretedBids[index]).to.have.property('width', expectedBid.w); + expect(interpretedBids[index]).to.have.property('height', expectedBid.h); + expect(interpretedBids[index]).to.have.property('creativeId', expectedBid.crid); + expect(interpretedBids[index]).to.have.property('currency', expectedBid.currency); + expect(interpretedBids[index]).to.have.property('netRevenue', expectedBid.netRevenue); + expect(interpretedBids[index]).to.have.property('ttl', expectedBid.ttl); + expect(interpretedBids[index]).to.have.property('ad', expectedBid.adm); + expect(interpretedBids[index]).to.have.property('mediaType', expectedBid.mtype); + }); + }); + + it('should return an empty array with empty response', function () { + const interpretedBids = spec.interpretResponse(emptyResponse); + expect(interpretedBids).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + const RESPONSE = { + body: { + ext: { + usersync: { + sovrn: { + syncs: [{type: 'iframe', url: 'https://sovrn.com/iframe_sync'}] + }, + appnexus: { + syncs: [{type: 'image', url: 'https://appnexus.com/image_sync'}] + } + } + } + } + }; + + it('should return empty array if no options are provided', function () { + const opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty array if neither iframe nor pixel is enabled', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return syncs only for iframe sync type', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync.sovrn.syncs[0].url); + }); + + it('should return syncs only for pixel sync types', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync.appnexus.syncs[0].url); + }); + + it('should return syncs when both iframe and pixel are enabled', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + expect(opts.length).to.equal(2); + }); + }); +}); diff --git a/test/spec/modules/pangleBidAdapter_spec.js b/test/spec/modules/pangleBidAdapter_spec.js index 6d8f9a66bcf..f2504a810c4 100644 --- a/test/spec/modules/pangleBidAdapter_spec.js +++ b/test/spec/modules/pangleBidAdapter_spec.js @@ -127,11 +127,15 @@ describe('pangle bid adapter', function () { describe('buildRequests', function () { it('creates request data', function () { - let request = spec.buildRequests(REQUEST, DEFAULT_OPTIONS)[0]; - expect(request).to.exist.and.to.be.a('object'); - const payload = request.data; - expect(payload.imp[0]).to.have.property('id', REQUEST[0].bidId); - expect(payload.imp[1]).to.have.property('id', REQUEST[1].bidId); + let request1 = spec.buildRequests(REQUEST, DEFAULT_OPTIONS)[0]; + expect(request1).to.exist.and.to.be.a('object'); + const payload1 = request1.data; + expect(payload1.imp[0]).to.have.property('id', REQUEST[0].bidId); + + let request2 = spec.buildRequests(REQUEST, DEFAULT_OPTIONS)[1]; + expect(request2).to.exist.and.to.be.a('object'); + const payload2 = request2.data; + expect(payload2.imp[0]).to.have.property('id', REQUEST[1].bidId); }); }); diff --git a/test/spec/modules/pstudioBidAdapter_spec.js b/test/spec/modules/pstudioBidAdapter_spec.js new file mode 100644 index 00000000000..52ecb820ed3 --- /dev/null +++ b/test/spec/modules/pstudioBidAdapter_spec.js @@ -0,0 +1,514 @@ +import { assert } from 'chai'; +import sinon from 'sinon'; +import { spec, storage } from 'modules/pstudioBidAdapter.js'; +import { deepClone } from '../../../src/utils.js'; + +describe('PStudioAdapter', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + const bannerBid = { + bidder: 'pstudio', + params: { + pubid: '258c2a8d-d2ad-4c31-a2a5-e63001186456', + floorPrice: 1.15, + }, + adUnitCode: 'test-div-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + pos: 0, + name: 'some-name', + }, + }, + bidId: '30b31c1838de1e', + }; + + const videoBid = { + bidder: 'pstudio', + params: { + pubid: '258c2a8d-d2ad-4c31-a2a5-e63001186456', + floorPrice: 1.15, + }, + adUnitCode: 'test-div-1', + mediaTypes: { + video: { + playerSize: [[300, 250]], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + protocols: [2, 3], + startdelay: 5, + placement: 2, + skip: 1, + skipafter: 1, + minbitrate: 10, + maxbitrate: 10, + delivery: 1, + playbackmethod: [1, 3], + api: [2], + linearity: 1, + }, + }, + bidId: '30b31c1838de1e', + }; + + const bidWithOptionalParams = deepClone(bannerBid); + bidWithOptionalParams.params['bcat'] = ['IAB17-18', 'IAB7-42']; + bidWithOptionalParams.params['badv'] = ['ford.com']; + bidWithOptionalParams.params['bapp'] = ['com.foo.mygame']; + bidWithOptionalParams.params['regs'] = { + coppa: 1, + }; + + bidWithOptionalParams.userId = { + uid2: { + id: '7505e78e-4a9b-4011-8901-0e00c3f55ea9', + }, + }; + + const emptyOrtb2BidderRequest = { ortb2: {} }; + + const baseBidderRequest = { + ortb2: { + device: { + w: 1680, + h: 342, + }, + }, + }; + + const extendedBidderRequest = deepClone(baseBidderRequest); + extendedBidderRequest.ortb2['device'] = { + dnt: 0, + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36', + lmt: 0, + ip: '192.0.0.1', + ipv6: '2001:0000:130F:0000:0000:09C0:876A:130B', + devicetype: 2, + make: 'some_producer', + model: 'some_model', + os: 'some_os', + osv: 'some_version', + js: 1, + language: 'en', + carrier: 'WiFi', + connectiontype: 0, + ifa: 'some_ifa', + geo: { + lat: 50.4, + lon: 40.2, + country: 'some_country_code', + region: 'some_region_code', + regionfips104: 'some_fips_code', + metro: 'metro_code', + city: 'city_code', + zip: 'zip_code', + type: 2, + }, + ext: { + ifatype: 'dpid', + }, + }; + extendedBidderRequest.ortb2['site'] = { + id: 'some_id', + 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', + publisher: { + name: 'some_name', + cat: ['IAB2'], + domain: 'https://page.example.com/here.html', + }, + content: { + id: 'some_id', + episode: 22, + title: 'New episode.', + series: 'New series.', + artist: 'New artist', + genre: 'some genre', + album: 'New album', + isrc: 'AA-6Q7-20-00047', + season: 'New season', + }, + mobile: 0, + }; + extendedBidderRequest.ortb2['app'] = { + id: 'some_id', + name: 'example', + bundle: 'some_bundle', + domain: 'page.example.com', + storeurl: 'https://store.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'], + ver: 'some_version', + privacypolicy: 0, + paid: 0, + keywords: 'some, example, keywords', + publisher: { + name: 'some_name', + cat: ['IAB2'], + domain: 'https://page.example.com/here.html', + }, + content: { + id: 'some_id', + episode: 22, + title: 'New episode.', + series: 'New series.', + artist: 'New artist', + genre: 'some genre', + album: 'New album', + isrc: 'AA-6Q7-20-00047', + season: 'New season', + }, + }; + extendedBidderRequest.ortb2['user'] = { + yob: 1992, + gender: 'M', + }; + extendedBidderRequest.ortb2['regs'] = { + coppa: 0, + }; + + describe('isBidRequestValid', function () { + it('should return true when publisher id found', function () { + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + }); + + it('should return true for video bid', () => { + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + + it('should return false when publisher id not found', function () { + const localBid = deepClone(bannerBid); + delete localBid.params.pubid; + delete localBid.params.floorPrice; + + expect(spec.isBidRequestValid(localBid)).to.equal(false); + }); + + it('should return false when playerSize in video not found', () => { + const localBid = deepClone(videoBid); + delete localBid.mediaTypes.video.playerSize; + + expect(spec.isBidRequestValid(localBid)).to.equal(false); + }); + + it('should return false when mimes in video not found', () => { + const localBid = deepClone(videoBid); + delete localBid.mediaTypes.video.mimes; + + expect(spec.isBidRequestValid(localBid)).to.equal(false); + }); + + it('should return false when protocols in video not found', () => { + const localBid = deepClone(videoBid); + delete localBid.mediaTypes.video.protocols; + + expect(spec.isBidRequestValid(localBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bannerRequest = spec.buildRequests([bannerBid], baseBidderRequest); + const bannerPayload = JSON.parse(bannerRequest[0].data); + const videoRequest = spec.buildRequests([videoBid], baseBidderRequest); + const videoPayload = JSON.parse(videoRequest[0].data); + + it('should properly map ids in request payload', function () { + expect(bannerPayload.id).to.equal(bannerBid.bidId); + expect(bannerPayload.adtagid).to.equal(bannerBid.adUnitCode); + }); + + it('should properly map banner mediaType in request payload', function () { + expect(bannerPayload.banner_properties).to.deep.equal({ + name: bannerBid.mediaTypes.banner.name, + sizes: bannerBid.mediaTypes.banner.sizes, + pos: bannerBid.mediaTypes.banner.pos, + }); + }); + + it('should properly map video mediaType in request payload', () => { + expect(videoPayload.video_properties).to.deep.equal({ + w: videoBid.mediaTypes.video.playerSize[0][0], + h: videoBid.mediaTypes.video.playerSize[0][1], + mimes: videoBid.mediaTypes.video.mimes, + minduration: videoBid.mediaTypes.video.minduration, + maxduration: videoBid.mediaTypes.video.maxduration, + protocols: videoBid.mediaTypes.video.protocols, + startdelay: videoBid.mediaTypes.video.startdelay, + placement: videoBid.mediaTypes.video.placement, + skip: videoBid.mediaTypes.video.skip, + skipafter: videoBid.mediaTypes.video.skipafter, + minbitrate: videoBid.mediaTypes.video.minbitrate, + maxbitrate: videoBid.mediaTypes.video.maxbitrate, + delivery: videoBid.mediaTypes.video.delivery, + playbackmethod: videoBid.mediaTypes.video.playbackmethod, + api: videoBid.mediaTypes.video.api, + linearity: videoBid.mediaTypes.video.linearity, + }); + }); + + it('should properly set required bidder params in request payload', function () { + expect(bannerPayload.pubid).to.equal(bannerBid.params.pubid); + expect(bannerPayload.floor_price).to.equal(bannerBid.params.floorPrice); + }); + + it('should omit optional bidder params or first-party data from bid request if they are not provided', function () { + assert.isUndefined(bannerPayload.bcat); + assert.isUndefined(bannerPayload.badv); + assert.isUndefined(bannerPayload.bapp); + assert.isUndefined(bannerPayload.user); + assert.isUndefined(bannerPayload.device); + assert.isUndefined(bannerPayload.site); + assert.isUndefined(bannerPayload.app); + assert.isUndefined(bannerPayload.user_ids); + assert.isUndefined(bannerPayload.regs); + }); + + it('should properly set optional bidder parameters', function () { + const request = spec.buildRequests( + [bidWithOptionalParams], + baseBidderRequest + ); + const payload = JSON.parse(request[0].data); + + expect(payload.bcat).to.deep.equal(['IAB17-18', 'IAB7-42']); + expect(payload.badv).to.deep.equal(['ford.com']); + expect(payload.bapp).to.deep.equal(['com.foo.mygame']); + }); + + it('should properly set optional user_ids', function () { + const request = spec.buildRequests( + [bidWithOptionalParams], + baseBidderRequest + ); + const { + user: { uid2_token }, + } = JSON.parse(request[0].data); + const expectedUID = '7505e78e-4a9b-4011-8901-0e00c3f55ea9'; + + expect(uid2_token).to.equal(expectedUID); + }); + + it('should properly set optional user_ids when no first party data is provided', function () { + const request = spec.buildRequests( + [bidWithOptionalParams], + emptyOrtb2BidderRequest + ); + const { + user: { uid2_token }, + } = JSON.parse(request[0].data); + const expectedUID = '7505e78e-4a9b-4011-8901-0e00c3f55ea9'; + + expect(uid2_token).to.equal(expectedUID); + }); + + it('should properly handle first-party data', function () { + const request = spec.buildRequests([bannerBid], extendedBidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.user).to.deep.equal(extendedBidderRequest.ortb2.user); + expect(payload.device).to.deep.equal(extendedBidderRequest.ortb2.device); + expect(payload.site).to.deep.equal(extendedBidderRequest.ortb2.site); + expect(payload.app).to.deep.equal(extendedBidderRequest.ortb2.app); + expect(payload.regs).to.deep.equal(extendedBidderRequest.ortb2.regs); + }); + + it('should not set first-party data if nothing is provided in ORTB2 param', function () { + const request = spec.buildRequests([bannerBid], emptyOrtb2BidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload).not.to.haveOwnProperty('user'); + expect(payload).not.to.haveOwnProperty('device'); + expect(payload).not.to.haveOwnProperty('site'); + expect(payload).not.to.haveOwnProperty('app'); + expect(payload).not.to.haveOwnProperty('regs'); + }); + + it('should set user id if proper cookie is present', function () { + const cookie = '157bc918-b961-4216-ac72-29fc6363edcb'; + sandbox.stub(storage, 'getCookie').returns(cookie); + + const request = spec.buildRequests([bannerBid], emptyOrtb2BidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.user.id).to.equal(cookie); + }); + + it('should not set user id if proper cookie not present', function () { + const request = spec.buildRequests([bannerBid], emptyOrtb2BidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload).not.to.haveOwnProperty('user'); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + id: '123141241231', + bids: [ + { + cpm: 1.02, + width: 300, + height: 600, + currency: 'USD', + ad: '

Hello ad

', + creative_id: 'crid12345', + net_revenue: true, + meta: { + advertiser_domains: ['https://advertiser.com'], + }, + }, + ], + }, + }; + + const serverVideoResponse = { + body: { + id: '123141241231', + bids: [ + { + vast_url: 'https://v.a/st.xml', + cpm: 5, + width: 640, + height: 480, + currency: 'USD', + creative_id: 'crid12345', + net_revenue: true, + meta: { + advertiser_domains: ['https://advertiser.com'], + }, + }, + ], + }, + }; + + const bidRequest = { + method: 'POST', + url: 'test-url', + data: JSON.stringify({ + id: '12345', + pubid: 'somepubid', + }), + }; + + it('should properly parse response from server', function () { + const expectedResponse = { + requestId: JSON.parse(bidRequest.data).id, + cpm: serverResponse.body.bids[0].cpm, + width: serverResponse.body.bids[0].width, + height: serverResponse.body.bids[0].height, + ad: serverResponse.body.bids[0].ad, + currency: serverResponse.body.bids[0].currency, + creativeId: serverResponse.body.bids[0].creative_id, + netRevenue: serverResponse.body.bids[0].net_revenue, + meta: { + advertiserDomains: + serverResponse.body.bids[0].meta.advertiser_domains, + }, + ttl: 300, + }; + const parsedResponse = spec.interpretResponse(serverResponse, bidRequest); + + expect(parsedResponse[0]).to.deep.equal(expectedResponse); + }); + + it('should properly parse video response from server', function () { + const expectedResponse = { + requestId: JSON.parse(bidRequest.data).id, + cpm: serverVideoResponse.body.bids[0].cpm, + width: serverVideoResponse.body.bids[0].width, + height: serverVideoResponse.body.bids[0].height, + currency: serverVideoResponse.body.bids[0].currency, + creativeId: serverVideoResponse.body.bids[0].creative_id, + netRevenue: serverVideoResponse.body.bids[0].net_revenue, + mediaType: 'video', + vastUrl: serverVideoResponse.body.bids[0].vast_url, + vastXml: undefined, + meta: { + advertiserDomains: + serverVideoResponse.body.bids[0].meta.advertiser_domains, + }, + ttl: 300, + }; + const parsedResponse = spec.interpretResponse( + serverVideoResponse, + bidRequest + ); + + expect(parsedResponse[0]).to.deep.equal(expectedResponse); + }); + + it('should return empty array if no bids are returned', function () { + const emptyResponse = deepClone(serverResponse); + emptyResponse.body.bids = undefined; + + const parsedResponse = spec.interpretResponse(emptyResponse, bidRequest); + + expect(parsedResponse).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function () { + it('should return sync object with correctly injected user id', function () { + sandbox.stub(storage, 'getCookie').returns('testid'); + + const result = spec.getUserSyncs({}, {}, {}, {}); + + expect(result).to.deep.equal([ + { + type: 'image', + url: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=k1on5ig&ttd_tpi=1&ttd_puid=testid&dsp=ttd', + }, + { + type: 'image', + url: 'https://dsp.myads.telkomsel.com/api/v1/pixel?uid=testid', + }, + ]); + }); + + it('should generate user id and put the same uuid it into sync object', function () { + sandbox.stub(storage, 'getCookie').returns(undefined); + + const result = spec.getUserSyncs({}, {}, {}, {}); + const url1 = result[0].url; + const url2 = result[1].url; + + const expectedUID1 = extractValueFromURL(url1, 'ttd_puid'); + const expectedUID2 = extractValueFromURL(url2, 'uid'); + + expect(expectedUID1).to.equal(expectedUID2); + + expect(result[0]).deep.equal({ + type: 'image', + url: `https://match.adsrvr.org/track/cmf/generic?ttd_pid=k1on5ig&ttd_tpi=1&ttd_puid=${expectedUID1}&dsp=ttd`, + }); + expect(result[1]).deep.equal({ + type: 'image', + url: `https://dsp.myads.telkomsel.com/api/v1/pixel?uid=${expectedUID2}`, + }); + // Helper function to extract UUID from URL + function extractValueFromURL(url, key) { + const match = url.match(new RegExp(`[?&]${key}=([^&]*)`)); + return match ? match[1] : null; + } + }); + }); +}); diff --git a/test/spec/modules/publirBidAdapter_spec.js b/test/spec/modules/publirBidAdapter_spec.js new file mode 100644 index 00000000000..60840b82efb --- /dev/null +++ b/test/spec/modules/publirBidAdapter_spec.js @@ -0,0 +1,488 @@ +import { expect } from 'chai'; +import { spec } from 'modules/publirBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { BANNER } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT = 'https://prebid.publir.com/publirPrebidEndPoint'; +const RTB_DOMAIN_TEST = 'prebid.publir.com'; +const TTL = 360; + +describe('publirAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('bid adapter', function () { + it('should have aliases', function () { + expect(spec.aliases).to.be.an('array').that.is.not.empty; + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'pubId': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when pubId is missing', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'pubId': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'pubId': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' + } + ]; + + const bidderRequest = { + bidderCode: 'publir', + } + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('sends bid request to rtbDomain ENDPOINT via POST', function () { + bidRequests[0].params.rtbDomain = RTB_DOMAIN_TEST; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should send the correct bid Id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); + config.setConfig({ + userSync: { + syncEnabled: true, + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); + }); + + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); + }); + + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); + }); + + it('should check sua param in bid request', function() { + const sua = { + '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': '', + 'bitness': '64', + 'architecture': 'x86' + } + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'device': { + 'sua': { + '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': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + const requestWithSua = spec.buildRequests([bid], bidderRequest); + const data = requestWithSua.data; + expect(data.bids[0].sua).to.exist; + expect(data.bids[0].sua).to.deep.equal(sua); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sua).to.not.exist; + }); + + describe('COPPA Param', function() { + it('should set coppa equal 0 in bid request if coppa is set to false', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(0); + }); + + it('should set coppa equal 1 in bid request if coppa is set to true', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'regs': { + 'coppa': true, + } + }; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(1); + }); + }); + }); + + describe('interpretResponse', function () { + const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [ + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER, + campId: '65902db45721d690ee0bc8c3' + }] + }; + + const expectedBannerResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 250, + ttl: TTL, + creativeId: '639153ddd0s443', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: BANNER, + meta: { + mediaType: BANNER, + ad_key: '9b5e00f2-8831-4efa-a933-c4f68710ffc0' + }, + ad: '""', + campId: '65902db45721d690ee0bc8c3', + bidder: 'publir' + }; + + it('should get correct bid response', function () { + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + } + }; + + const iframeSyncResponse = { + body: { + params: { + userSyncURL: 'https://iframe-sync-url.test' + } + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'pubId': 'jdye8weeyirk00000001' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) +}); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index c6447905ecd..951b5135260 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -100,7 +100,7 @@ const BID2 = Object.assign({}, BID, { adserverTargeting: { 'hb_bidder': 'pubmatic', 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', + 'hb_pb': 1.50, 'hb_size': '728x90', 'hb_source': 'server' }, @@ -606,6 +606,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); expect(data.s[0].ps[0].frv).to.equal(1.1); + expect(data.s[0].ps[0].pb).to.equal(1.2); // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); @@ -640,6 +641,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); // tracker slot1 let firstTracker = requests[0].url; @@ -812,6 +814,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); // tracker slot1 let firstTracker = requests[0].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); @@ -1035,6 +1038,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); }); it('Logger: currency conversion check', function() { @@ -1149,6 +1153,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); expect(data.dvc).to.deep.equal({'plt': 2}); // respective tracker slot let firstTracker = requests[1].url; @@ -1209,6 +1214,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.dvc).to.deep.equal({'plt': 1}); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); // respective tracker slot let firstTracker = requests[1].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); @@ -1267,6 +1273,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); // respective tracker slot let firstTracker = requests[1].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); @@ -1388,6 +1395,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); }); it('Logger: best case + win tracker in case of Bidder Aliases', function() { @@ -1466,6 +1474,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); expect(data.s[0].ps[0].frv).to.equal(1.1); + expect(data.s[0].ps[0].pb).to.equal(1.2); // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); @@ -1501,6 +1510,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.s[1].ps[0].frv).to.equal(1.1); + expect(data.s[1].ps[0].pb).to.equal(1.50); // tracker slot1 let firstTracker = requests[0].url; @@ -1596,6 +1606,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); expect(data.s[0].ps[0].frv).to.equal(1.1); + expect(data.s[0].ps[0].pb).to.equal(1.2); // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 5d59ff99a89..fda2c853e87 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1781,6 +1781,37 @@ describe('PubMatic adapter', function () { expect(data2.regs).to.equal(undefined);// USP/CCPAs }); + it('Request params should include DSA signals if present', function () { + const dsa = { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [ + { + domain: 'platform1domain.com', + dsaparams: [1] + }, + { + domain: 'SSP2domain.com', + dsaparams: [1, 2] + } + ] + }; + + let bidRequest = { + ortb2: { + regs: { + ext: { + dsa + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + assert.deepEqual(data.regs.ext.dsa, dsa); + }); + it('Request params check with JW player params', function() { let bidRequests = [ { @@ -3753,6 +3784,16 @@ describe('PubMatic adapter', function () { describe('Preapare metadata', function () { it('Should copy all fields from ext to meta', function () { + const dsa = { + behalf: 'Advertiser', + paid: 'Advertiser', + transparency: [{ + domain: 'dsp1domain.com', + dsaparams: [1, 2] + }], + adrender: 1 + }; + const bid = { 'adomain': [ 'mystartab.com' @@ -3764,6 +3805,7 @@ describe('PubMatic adapter', function () { 'deal_channel': 1, 'bidtype': 0, advertiserId: 'adid', + dsa, // networkName: 'nwnm', // primaryCatId: 'pcid', // advertiserName: 'adnm', @@ -3795,6 +3837,7 @@ describe('PubMatic adapter', function () { expect(br.meta.secondaryCatIds[0]).to.equal('IAB_CATEGORY'); expect(br.meta.advertiserDomains).to.be.an('array').with.length.above(0); // adomain expect(br.meta.clickUrl).to.equal('mystartab.com'); // adomain + expect(br.meta.dsa).to.equal(dsa); // dsa }); it('Should be empty, when ext and adomain is absent in bid object', function () { diff --git a/test/spec/modules/rasBidAdapter_spec.js b/test/spec/modules/rasBidAdapter_spec.js index 719e15ad695..f172d192221 100644 --- a/test/spec/modules/rasBidAdapter_spec.js +++ b/test/spec/modules/rasBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/rasBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import {getAdUnitSizes} from '../../../src/utils'; const CSR_ENDPOINT = 'https://csr.onet.pl/4178463/csr-006/csr.json?nid=4178463&'; @@ -64,6 +63,20 @@ describe('rasBidAdapter', function () { customParams: { test: 'name=value' } + }, + mediaTypes: { + banner: { + sizes: [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } } }; const bid2 = { @@ -75,6 +88,16 @@ describe('rasBidAdapter', function () { area: 'areatest', site: 'test', network: '4178463' + }, + mediaTypes: { + banner: { + sizes: [ + [ + 750, + 300 + ] + ] + } } }; @@ -128,6 +151,7 @@ describe('rasBidAdapter', function () { } }; const requests = spec.buildRequests([bidCopy, bid2]); + expect(requests[0].url).to.have.string(CSR_ENDPOINT); expect(requests[0].url).to.have.string('slot0=test'); expect(requests[0].url).to.have.string('id0=1'); @@ -148,6 +172,39 @@ describe('rasBidAdapter', function () { expect(requests[0].url).to.have.string('kvadunit=test%2Fareatest'); expect(requests[0].url).to.have.string('pos0=0'); }); + + it('should parse dsainfo when available', function () { + const bidCopy = { ...bid }; + bidCopy.params = { + ...bid.params, + pageContext: { + dv: 'test/areatest', + du: 'https://example.com/', + dr: 'https://example.org/', + keyWords: ['val1', 'val2'], + keyValues: { + adunit: 'test/areatest' + } + } + }; + let bidderRequest = { + ortb2: { + regs: { + ext: { + dsa: { + required: 1 + } + } + } + } + }; + let requests = spec.buildRequests([bidCopy], bidderRequest); + expect(requests[0].url).to.have.string('dsainfo=1'); + + bidderRequest.ortb2.regs.ext.dsa.required = 0; + requests = spec.buildRequests([bidCopy], bidderRequest); + expect(requests[0].url).to.have.string('dsainfo=0'); + }); }); describe('interpretResponse', function () { @@ -172,7 +229,7 @@ describe('rasBidAdapter', function () { it('should get correct bid response', function () { const resp = spec.interpretResponse({ body: response }, { bidIds: [{ slot: 'flat-belkagorna', bidId: 1 }] }); - expect(resp[0]).to.have.all.keys('cpm', 'currency', 'netRevenue', 'requestId', 'ttl', 'width', 'height', 'creativeId', 'dealId', 'ad', 'meta'); + expect(resp[0]).to.have.all.keys('cpm', 'currency', 'netRevenue', 'requestId', 'ttl', 'width', 'height', 'creativeId', 'dealId', 'ad', 'meta', 'actgMatch', 'mediaType'); expect(resp.length).to.equal(1); }); @@ -245,4 +302,309 @@ describe('rasBidAdapter', function () { expect(resp).to.deep.equal({bids: [], fledgeAuctionConfigs: auctionConfigs}); }); }); + + describe('buildNativeRequests', function () { + const bid = { + sizes: 'fluid', + bidder: 'ras', + bidId: 1, + params: { + slot: 'nativestd', + area: 'areatest', + site: 'test', + slotSequence: '0', + network: '4178463', + customParams: { + test: 'name=value' + } + }, + mediaTypes: { + native: { + clickUrl: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + len: 25, + required: true + }, + title: { + len: 50, + required: true + } + } + } + }; + + it('should parse bids to native request', function () { + const requests = spec.buildRequests([bid], { + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': 'some-consent-string' + }, + 'refererInfo': { + 'ref': 'https://example.org/', + 'page': 'https://example.com/' + } + }); + + expect(requests[0].url).to.have.string(CSR_ENDPOINT); + expect(requests[0].url).to.have.string('slot0=nativestd'); + expect(requests[0].url).to.have.string('id0=1'); + expect(requests[0].url).to.have.string('site=test'); + expect(requests[0].url).to.have.string('area=areatest'); + expect(requests[0].url).to.have.string('cre_format=html'); + expect(requests[0].url).to.have.string('systems=das'); + expect(requests[0].url).to.have.string('ems_url=1'); + expect(requests[0].url).to.have.string('bid_rate=1'); + expect(requests[0].url).to.have.string('gdpr_applies=true'); + expect(requests[0].url).to.have.string('euconsent=some-consent-string'); + expect(requests[0].url).to.have.string('du=https%3A%2F%2Fexample.com%2F'); + expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); + expect(requests[0].url).to.have.string('test=name%3Dvalue'); + expect(requests[0].url).to.have.string('cre_format0=native'); + expect(requests[0].url).to.have.string('iusizes0=fluid'); + }); + }); + + describe('interpretNativeResponse', function () { + const response = { + 'adsCheck': 'ok', + 'geoloc': {}, + 'ir': '92effd60-0c84-4dac-817e-763ea7b8ac65', + 'iv': '202003191334467636346500', + 'ads': [ + { + 'id': 'nativestd', + 'slot': 'nativestd', + 'prio': 10, + 'type': 'native', + 'bid_rate': 0.321123, + 'adid': 'das,50463,152276', + 'id_3': '12734' + } + ] + }; + const responseTeaserStandard = { + adsCheck: 'ok', + geoloc: {}, + ir: '92effd60-0c84-4dac-817e-763ea7b8ac65', + iv: '202003191334467636346500', + ads: [ + { + id: 'nativestd', + slot: 'nativestd', + prio: 10, + type: 'native', + bid_rate: 0.321123, + adid: 'das,50463,152276', + id_3: '12734', + data: { + fields: { + leadtext: 'BODY', + title: 'Headline', + image: '//img.url', + url: '//link.url', + impression: '//impression.url', + impression1: '//impression1.url', + impressionJs1: '//impressionJs1.url' + }, + meta: { + slot: 'nativestd', + height: 1, + width: 1, + advertiser_name: 'Test Onet', + dsaurl: '//dsa.url', + adclick: '//adclick.url' + } + }, + ems_link: '//ems.url' + } + ] + }; + const responseNativeInFeed = { + adsCheck: 'ok', + geoloc: {}, + ir: '92effd60-0c84-4dac-817e-763ea7b8ac65', + iv: '202003191334467636346500', + ads: [ + { + id: 'nativestd', + slot: 'nativestd', + prio: 10, + type: 'native', + bid_rate: 0.321123, + adid: 'das,50463,152276', + id_3: '12734', + data: { + fields: { + Body: 'BODY', + Calltoaction: 'Calltoaction', + Headline: 'Headline', + Image: '//img.url', + Sponsorlabel: 'nie', + Thirdpartyclicktracker: '//link.url', + imp: '//imp.url' + }, + meta: { + slot: 'nativestd', + height: 1, + width: 1, + advertiser_name: 'Test Onet', + dsaurl: '//dsa.url', + adclick: '//adclick.url' + } + }, + ems_link: '//ems.url' + } + ] + }; + const expectedTeaserStandardOrtbResponse = { + ver: '1.2', + assets: [ + { + id: 2, + img: { + url: '//img.url', + w: 1, + h: 1 + } + }, + { + id: 4, + title: { + text: 'Headline' + } + }, + { + id: 3, + data: { + value: 'Test Onet', + type: 1 + } + } + ], + link: { + url: '//adclick.url//link.url' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: '//ems.url' + }, + { + event: 1, + method: 1, + url: '//impression.url' + }, + { + event: 1, + method: 1, + url: '//impression1.url' + }, + { + event: 1, + method: 2, + url: '//impressionJs1.url' + } + ], + privacy: '//dsa.url' + }; + const expectedTeaserStandardResponse = { + sendTargetingKeys: false, + title: 'Headline', + image: { + url: '//img.url', + width: 1, + height: 1 + }, + clickUrl: '//adclick.url//link.url', + cta: '', + body: 'BODY', + sponsoredBy: 'Test Onet', + ortb: expectedTeaserStandardOrtbResponse, + privacyLink: '//dsa.url' + }; + const expectedNativeInFeedOrtbResponse = { + ver: '1.2', + assets: [ + { + id: 2, + img: { + url: '//img.url', + w: 1, + h: 1 + } + }, + { + id: 4, + title: { + text: 'Headline' + } + }, + { + id: 3, + data: { + value: 'Test Onet', + type: 1 + } + } + ], + link: { + url: '//adclick.url//link.url' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: '//ems.url' + }, + { + event: 1, + method: 1, + url: '//imp.url' + } + ], + privacy: '//dsa.url', + }; + const expectedNativeInFeedResponse = { + sendTargetingKeys: false, + title: 'Headline', + image: { + url: '//img.url', + width: 1, + height: 1 + }, + clickUrl: '//adclick.url//link.url', + cta: 'Calltoaction', + body: 'BODY', + sponsoredBy: 'Test Onet', + ortb: expectedNativeInFeedOrtbResponse, + privacyLink: '//dsa.url' + }; + + it('should get correct bid native response', function () { + const resp = spec.interpretResponse({ body: response }, { bidIds: [{ slot: 'nativestd', bidId: 1, mediaType: 'native' }] }); + + expect(resp[0]).to.have.all.keys('cpm', 'currency', 'netRevenue', 'requestId', 'ttl', 'width', 'height', 'creativeId', 'dealId', 'meta', 'actgMatch', 'mediaType', 'native'); + expect(resp.length).to.equal(1); + }); + + it('should get correct native response for TeaserStandard', function () { + const resp = spec.interpretResponse({ body: responseTeaserStandard }, { bidIds: [{ slot: 'nativestd', bidId: 1, mediaType: 'native' }] }); + const teaserStandardResponse = resp[0].native; + + expect(JSON.stringify(teaserStandardResponse)).to.equal(JSON.stringify(expectedTeaserStandardResponse)); + }); + + it('should get correct native response for NativeInFeed', function () { + const resp = spec.interpretResponse({ body: responseNativeInFeed }, { bidIds: [{ slot: 'nativestd', bidId: 1, mediaType: 'native' }] }); + const nativeInFeedResponse = resp[0].native; + + expect(JSON.stringify(nativeInFeedResponse)).to.equal(JSON.stringify(expectedNativeInFeedResponse)); + }); + }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 184bea07d04..55e8909f6c8 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -34,6 +34,7 @@ describe('the rubicon adapter', function () { logErrorSpy; /** + * @typedef {import('../../../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {Object} sizeMapConverted * @property {string} sizeId * @property {string} size @@ -2267,17 +2268,6 @@ describe('the rubicon adapter', function () { expect(payload.ext.prebid.analytics).to.be.undefined; }); - it('should send video exp param correctly when set', function () { - const bidderRequest = createVideoBidderRequest(); - config.setConfig({s2sConfig: {defaultTtl: 600}}); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; - - // should exp set to the right value according to config - let imp = post.imp[0]; - expect(imp.exp).to.equal(600); - }); - it('should not send video exp at all if not set in s2sConfig config', function () { const bidderRequest = createVideoBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -3086,7 +3076,7 @@ describe('the rubicon adapter', function () { expect(bids[0].width).to.equal(320); expect(bids[0].height).to.equal(50); expect(bids[0].cpm).to.equal(0.911); - expect(bids[0].ttl).to.equal(300); + expect(bids[0].ttl).to.equal(360); expect(bids[0].netRevenue).to.equal(true); expect(bids[0].rubicon.advertiserId).to.equal(7); expect(bids[0].rubicon.networkId).to.equal(8); @@ -3103,7 +3093,7 @@ describe('the rubicon adapter', function () { expect(bids[1].width).to.equal(300); expect(bids[1].height).to.equal(250); expect(bids[1].cpm).to.equal(0.811); - expect(bids[1].ttl).to.equal(300); + expect(bids[1].ttl).to.equal(360); expect(bids[1].netRevenue).to.equal(true); expect(bids[1].rubicon.advertiserId).to.equal(7); expect(bids[1].rubicon.networkId).to.equal(8); @@ -3915,7 +3905,7 @@ describe('the rubicon adapter', function () { expect(bids[0].seatBidId).to.equal('0'); expect(bids[0].creativeId).to.equal('4259970'); expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); + expect(bids[0].ttl).to.equal(360); expect(bids[0].netRevenue).to.equal(true); expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); expect(bids[0].mediaType).to.equal('video'); @@ -4007,7 +3997,7 @@ describe('the rubicon adapter', function () { expect(bids[0].seatBidId).to.equal('0'); expect(bids[0].creativeId).to.equal('4259970'); expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); + expect(bids[0].ttl).to.equal(360); expect(bids[0].netRevenue).to.equal(true); expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); expect(bids[0].mediaType).to.equal('video'); diff --git a/test/spec/modules/setupadBidAdapter_spec.js b/test/spec/modules/setupadBidAdapter_spec.js new file mode 100644 index 00000000000..d4ff73d005f --- /dev/null +++ b/test/spec/modules/setupadBidAdapter_spec.js @@ -0,0 +1,348 @@ +import { spec } from 'modules/setupadBidAdapter.js'; + +describe('SetupadAdapter', function () { + const userIdAsEids = [ + { + source: 'pubcid.org', + uids: [ + { + atype: 1, + id: '01EAJWWNEPN3CYMM5N8M5VXY22', + }, + ], + }, + ]; + + const bidRequests = [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, + ]; + + const bidderRequest = { + ortb2: { + device: { + w: 1500, + h: 1000, + }, + }, + refererInfo: { + domain: 'test.com', + page: 'http://test.com', + ref: '', + }, + }; + + const serverResponse = { + body: { + id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', + seatbid: [ + { + bid: [ + { + id: 'test-bid-id', + price: 0.8, + adm: 'this is an ad', + adid: 'test-ad-id', + adomain: ['test.addomain.com'], + w: 300, + h: 250, + }, + ], + seat: 'testBidder', + }, + ], + cur: 'USD', + ext: { + sync: { + image: ['urlA?gdpr={{.GDPR}}'], + iframe: ['urlB'], + }, + }, + }, + }; + + describe('isBidRequestValid', function () { + const bid = { + bidder: 'setupad', + params: { + placement_id: '123', + }, + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when required params are not passed', function () { + delete bid.params.placement_id; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('check request params with GDPR and USP', function () { + const request = spec.buildRequests(bidRequests, bidRequests[0]); + expect(JSON.parse(request[0].data).user.ext.consent).to.equal( + 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA' + ); + expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); + expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); + }); + + it('check request params without GDPR', function () { + let bidRequestsWithoutGDPR = Object.assign({}, bidRequests[0]); + delete bidRequestsWithoutGDPR.gdprConsent; + const request = spec.buildRequests([bidRequestsWithoutGDPR], bidRequestsWithoutGDPR); + expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; + expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); + }); + + it('should return correct storedrequest id if account_id is provided', function () { + const request = spec.buildRequests(bidRequests, bidRequests[0]); + expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('test-account-id'); + }); + + it('should return correct storedrequest id if account_id is not provided', function () { + let bidRequestsWithoutAccountId = Object.assign({}, bidRequests[0]); + delete bidRequestsWithoutAccountId.params.account_id; + const request = spec.buildRequests( + [bidRequestsWithoutAccountId], + bidRequestsWithoutAccountId + ); + expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('default'); + }); + + it('validate generated params', function () { + const request = spec.buildRequests(bidRequests); + expect(request[0].bidId).to.equal('22c4871113f461'); + expect(JSON.parse(request[0].data).id).to.equal('15246a574e859f'); + }); + + it('check if correct site object was added', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const siteObj = JSON.parse(request[0].data).site; + + expect(siteObj.domain).to.equal('test.com'); + expect(siteObj.page).to.equal('http://test.com'); + expect(siteObj.ref).to.equal(''); + }); + + it('check if correct device object was added', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const deviceObj = JSON.parse(request[0].data).device; + + expect(deviceObj.w).to.equal(1500); + expect(deviceObj.h).to.equal(1000); + }); + + it('check if imp object was added', function () { + const request = spec.buildRequests(bidRequests); + expect(JSON.parse(request[0].data).imp).to.be.an('array'); + }); + + it('should send "user.ext.eids" in the request for Prebid.js supported modules only', function () { + const request = spec.buildRequests(bidRequests); + expect(JSON.parse(request[0].data).user.ext.eids).to.deep.equal(userIdAsEids); + }); + + it('should send an undefined "user.ext.eids" in the request if userId module is unsupported', function () { + let bidRequestsUnsupportedUserIdModule = Object.assign({}, bidRequests[0]); + delete bidRequestsUnsupportedUserIdModule.userIdAsEids; + const request = spec.buildRequests(bidRequestsUnsupportedUserIdModule); + + expect(JSON.parse(request[0].data).user.ext.eids).to.be.undefined; + }); + }); + + describe('getUserSyncs', () => { + it('should return user sync', () => { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true, + }; + const responses = [ + { + 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://cookie.stpd.cloud/sync?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(syncOptions, responses, gdprConsent, uspConsent); + + expect(userSyncs).to.deep.equal(expectedUserSyncs); + }); + + it('should return empty user syncs when responsetimemillis is not defined', () => { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true, + }; + const responses = [ + { + body: { + ext: {}, + }, + }, + ]; + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + }; + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb'; + const expectedUserSyncs = []; + + const userSyncs = spec.getUserSyncs(syncOptions, responses, gdprConsent, uspConsent); + + expect(userSyncs).to.deep.equal(expectedUserSyncs); + }); + }); + + describe('interpretResponse', function () { + it('should return empty array if error during parsing', () => { + const wrongServerResponse = 'wrong data'; + let request = spec.buildRequests(bidRequests, bidRequests[0]); + let result = spec.interpretResponse(wrongServerResponse, request); + + expect(result).to.be.instanceof(Array); + expect(result.length).to.equal(0); + }); + + it('should get correct bid response', function () { + const result = spec.interpretResponse(serverResponse, bidRequests[0]); + expect(result).to.be.an('array').with.lengthOf(1); + expect(result[0].requestId).to.equal('22c4871113f461'); + expect(result[0].cpm).to.equal(0.8); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('test-bid-id'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(360); + expect(result[0].ad).to.equal('this is an ad'); + }); + }); + + describe('onBidWon', function () { + it('should stop if bidder is not equal to BIDDER_CODE', function () { + const bid = { + bidder: 'rubicon', + }; + const result = spec.onBidWon(bid); + expect(result).to.be.undefined; + }); + + it('should stop if bid.params is not provided', function () { + const bid = { + bidder: 'setupad', + }; + const result = spec.onBidWon(bid); + expect(result).to.be.undefined; + }); + + it('should stop if bid.params is empty array', function () { + const bid = { + bidder: 'setupad', + params: [], + }; + const result = spec.onBidWon(bid); + expect(result).to.be.undefined; + }); + + it('should stop if bid.params is not array', function () { + expect( + spec.onBidWon({ + bidder: 'setupad', + params: {}, + }) + ).to.be.undefined; + + expect( + spec.onBidWon({ + bidder: 'setupad', + params: 'test', + }) + ).to.be.undefined; + + expect( + spec.onBidWon({ + bidder: 'setupad', + params: 1, + }) + ).to.be.undefined; + + expect( + spec.onBidWon({ + bidder: 'setupad', + params: null, + }) + ).to.be.undefined; + + expect( + spec.onBidWon({ + bidder: 'setupad', + params: undefined, + }) + ).to.be.undefined; + }); + + it('should stop if bid.params.placement_id is not provided', function () { + const bid = { + bidder: 'setupad', + params: [{ account_id: 'test' }], + }; + const result = spec.onBidWon(bid); + expect(result).to.be.undefined; + }); + + it('should stop if bid.params is not provided and bid.bids is not an array', function () { + const bid = { + bidder: 'setupad', + params: undefined, + bids: {}, + }; + const result = spec.onBidWon(bid); + expect(result).to.be.undefined; + }); + }); +}); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 1bb6f898b81..ab099d87429 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -425,6 +425,41 @@ describe('sharethrough adapter spec', function () { }); }); + describe('dsa', () => { + it('should properly attach dsa information to the request when applicable', () => { + bidderRequest.ortb2 = { + regs: { + ext: { + dsa: { + 'dsarequired': 1, + 'pubrender': 0, + 'datatopub': 1, + 'transparency': [{ + 'domain': 'good-domain', + 'dsaparams': [1, 2] + }, { + 'domain': 'bad-setup', + 'dsaparams': ['1', 3] + }] + } + } + } + } + + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; + expect(openRtbReq.regs.ext.dsa.dsarequired).to.equal(1); + expect(openRtbReq.regs.ext.dsa.pubrender).to.equal(0); + expect(openRtbReq.regs.ext.dsa.datatopub).to.equal(1); + expect(openRtbReq.regs.ext.dsa.transparency).to.deep.equal([{ + 'domain': 'good-domain', + 'dsaparams': [1, 2] + }, { + 'domain': 'bad-setup', + 'dsaparams': ['1', 3] + }]); + }); + }); + describe('transaction id at the impression level', () => { it('should include transaction id when provided', () => { const requests = spec.buildRequests(bidRequests, bidderRequest); diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 458ccc37759..1b592e142c3 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -61,7 +61,7 @@ describe('SmartyadsAdapter', 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', 'coppa'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa', 'eeid', 'ifa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.coppa).to.be.a('number'); diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js index 973e90abd5a..d0363eab144 100644 --- a/test/spec/modules/sovrnAnalyticsAdapter_spec.js +++ b/test/spec/modules/sovrnAnalyticsAdapter_spec.js @@ -12,8 +12,8 @@ let constants = require('src/constants.json'); /** * Emit analytics events - * @param {Array} eventArr - array of objects to define the events that will fire - * @param {object} eventObj - key is eventType, value is event + * @param {Array} eventType - array of objects to define the events that will fire + * @param {object} event - key is eventType, value is event * @param {string} auctionId - the auction id to attached to the events */ function emitEvent(eventType, event, auctionId) { diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index f165a6da6d1..274192d14a7 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -530,6 +530,45 @@ describe('sovrnBidAdapter', function() { expect(impression.bidfloor).to.equal(2.00) }) + it('floor should be undefined if there is no floor from the floor module and params', function() { + const floorBid = { + ...baseBidRequest + } + floorBid.params = { + tagid: 1234 + } + const request = spec.buildRequests([floorBid], baseBidderRequest) + const impression = JSON.parse(request.data).imp[0] + + expect(impression.bidfloor).to.be.undefined + }) + it('floor should be undefined if there is incorrect floor value from the floor module', function() { + const floorBid = { + ...baseBidRequest, + getFloor: () => ({currency: 'USD', floor: 'incorrect_value'}), + params: { + tagid: 1234 + } + } + const request = spec.buildRequests([floorBid], baseBidderRequest) + const impression = JSON.parse(request.data).imp[0] + + expect(impression.bidfloor).to.be.undefined + }) + it('floor should be undefined if there is incorrect floor value from the params', function() { + const floorBid = { + ...baseBidRequest, + getFloor: () => ({}) + } + floorBid.params = { + tagid: 1234, + bidfloor: 'incorrect_value' + } + const request = spec.buildRequests([floorBid], baseBidderRequest) + const impression = JSON.parse(request.data).imp[0] + + expect(impression.bidfloor).to.be.undefined + }) describe('First Party Data', function () { it('should provide first party data if provided', function() { const ortb2 = { diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 71619424e4b..2f5fe104eb1 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -638,7 +638,7 @@ describe('SSPBC adapter', function () { expect(adcode).to.be.a('string'); expect(adcode).to.contain('window.rekid'); expect(adcode).to.contain('window.mcad'); - expect(adcode).to.contain('window.gdpr'); + expect(adcode).to.contain('window.tcString'); expect(adcode).to.contain('window.page'); expect(adcode).to.contain('window.requestPVID'); }); diff --git a/test/spec/modules/stnBidAdapter_spec.js b/test/spec/modules/stnBidAdapter_spec.js new file mode 100644 index 00000000000..deba87baac2 --- /dev/null +++ b/test/spec/modules/stnBidAdapter_spec.js @@ -0,0 +1,625 @@ +import { expect } from 'chai'; +import { spec } from 'modules/stnBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT = 'https://hb.stngo.com/hb-multi'; +const TEST_ENDPOINT = 'https://hb.stngo.com/hb-multi-test'; +const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ + +describe('stnAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + } + }, + 'vastXml': '"..."' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'loop': 2, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'stn', + } + const placementId = '12345678'; + const api = [1, 2]; + const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; + const protocols = [2, 3, 5, 6]; + + it('sends the placementId to ENDPOINT via POST', function () { + bidRequests[0].params.placementId = placementId; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].placementId).to.equal(placementId); + }); + + it('sends the plcmt to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].plcmt).to.equal(1); + }); + + 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); + expect(request.method).to.equal('POST'); + }); + + it('sends bid request to TEST ENDPOINT via POST', function () { + const request = spec.buildRequests(testModeBidRequests, bidderRequest); + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should send the correct bid Id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); + }); + + it('should send the correct supported api array', function () { + bidRequests[0].mediaTypes.video.api = api; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].api).to.be.an('array'); + expect(request.data.bids[0].api).to.eql([1, 2]); + }); + + it('should send the correct mimes array', function () { + bidRequests[1].mediaTypes.banner.mimes = mimes; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[1].mimes).to.be.an('array'); + expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + }); + + it('should send the correct protocols array', function () { + bidRequests[0].mediaTypes.video.protocols = protocols; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].protocols).to.be.an('array'); + expect(request.data.bids[0].protocols).to.eql([2, 3, 5, 6]); + }); + + it('should send the correct sizes array', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sizes).to.be.an('array'); + expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) + expect(request.data.bids[1].sizes).to.be.an('array'); + expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + }); + + it('should send the correct media type', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].mediaType).to.equal(VIDEO) + expect(request.data.bids[1].mediaType).to.equal(BANNER) + }); + + it('should send the correct currency in bid request', function () { + const bid = utils.deepClone(bidRequests[0]); + bid.params = { + 'currency': 'EUR' + }; + const expectedCurrency = bid.params.currency; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].currency).to.equal(expectedCurrency); + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); + config.setConfig({ + userSync: { + syncEnabled: true, + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); + }); + + it('should not send the gpp param if gppConsent is false in the bidRequest', function () { + const bidderRequestWithGPP = Object.assign({gppConsent: false}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGPP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gpp'); + expect(request.data.params).to.not.have.property('gpp_sid'); + }); + + it('should send the gpp param if gppConsent is true in the bidRequest', function () { + const bidderRequestWithGPP = Object.assign({gppConsent: {gppString: 'test-consent-string', applicableSections: [7]}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGPP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gpp', 'test-consent-string'); + expect(request.data.params.gpp_sid[0]).to.be.equal(7); + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); + }); + + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); + }); + + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); + }); + + it('should check sua param in bid request', function() { + const sua = { + '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': '', + 'bitness': '64', + 'architecture': 'x86' + } + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'device': { + 'sua': { + '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': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + const requestWithSua = spec.buildRequests([bid], bidderRequest); + const data = requestWithSua.data; + expect(data.bids[0].sua).to.exist; + expect(data.bids[0].sua).to.deep.equal(sua); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sua).to.not.exist; + }); + + describe('COPPA Param', function() { + it('should set coppa equal 0 in bid request if coppa is set to false', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(0); + }); + + it('should set coppa equal 1 in bid request if coppa is set to true', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'regs': { + 'coppa': true, + } + }; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(1); + }); + }); + }); + + describe('interpretResponse', function () { + const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [{ + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: VIDEO + }, + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER + }] + }; + + const expectedVideoResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: VIDEO, + meta: { + mediaType: VIDEO, + advertiserDomains: ['abc.com'] + }, + vastXml: '', + }; + + const expectedBannerResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: BANNER, + meta: { + mediaType: BANNER, + advertiserDomains: ['abc.com'] + }, + ad: '""' + }; + + it('should get correct bid response', function () { + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + + it('video type should have vastXml key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml) + }); + + it('banner type should have ad key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[1].ad).to.equal(expectedBannerResponse.ad) + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + } + }; + + const iframeSyncResponse = { + body: { + params: { + userSyncURL: 'https://iframe-sync-url.test' + } + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) +}); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 8a121865cf2..ca09fbbbcc9 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -157,7 +157,7 @@ describe('Taboola Adapter', function () { spec.onBidderError({error, bidderRequest}); expect(server.requests[0].method).to.equal('POST'); expect(server.requests[0].url).to.equal(EVENT_ENDPOINT + '/bidError'); - expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(error, bidderRequest); + expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal({error, bidderRequest}); }); }); @@ -398,6 +398,30 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.ext.example).to.deep.equal(bidderRequest.ortb2.ext.example); }); + + it('should pass additional parameter in request for topics', function () { + const ortb2 = { + ...commonBidderRequest, + ortb2: { + user: { + data: { + segment: [ + { + id: '243' + } + ], + name: 'pa.taboola.com', + ext: { + segclass: '4', + segtax: 601 + } + } + } + } + } + const res = spec.buildRequests([defaultBidRequest], {...ortb2}) + expect(res.data.user.data).to.deep.equal(ortb2.ortb2.user.data); + }); }); describe('handle privacy segments when building request', function () { @@ -668,6 +692,181 @@ describe('Taboola Adapter', function () { } }; + const serverResponseWithPa = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': request.data.imp[0].id, + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD', + 'ext': { + 'igbid': [ + { + 'impid': request.data.imp[0].id, + 'igbuyer': [ + { + 'origin': 'https://pa.taboola.com', + 'buyerdata': '{\"seller\":\"pa.taboola.com\",\"resolveToConfig\":false,\"perBuyerSignals\":{\"https://pa.taboola.com\":{\"country\":\"US\",\"route\":\"AM\",\"cct\":[0.02241223,-0.8686833,0.96153843],\"vct\":\"-1967600173\",\"ccv\":null,\"ect\":[-0.13584597,2.5825605],\"ri\":\"100fb73d4064bc\",\"vcv\":\"165229814\",\"ecv\":[-0.39882636,-0.05216012],\"publisher\":\"test-headerbidding\",\"platform\":\"DESK\"}},\"decisionLogicUrl\":\"https://pa.taboola.com/score/decisionLogic.js\",\"sellerTimeout\":100,\"interestGroupBuyers\":[\"https://pa.taboola.com\"],\"perBuyerTimeouts\":{\"*\":50}}' + } + ] + } + ] + } + } + }; + + const serverResponseWithPartialPa = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': request.data.imp[0].id, + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD', + 'ext': { + 'igbid': [ + { + 'impid': request.data.imp[0].id, + 'igbuyer': [ + { + 'origin': 'https://pa.taboola.com', + 'buyerdata': '{}' + } + ] + } + ] + } + } + }; + + const serverResponseWithWrongPa = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': request.data.imp[0].id, + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD', + 'ext': { + 'igbid': [ + { + 'impid': request.data.imp[0].id, + 'igbuyer': [ + { + } + ] + } + ] + } + } + }; + + const serverResponseWithEmptyIgbidWIthWrongPa = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': request.data.imp[0].id, + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD', + 'ext': { + 'igbid': [ + { + } + ] + } + } + }; + it('should return empty array if no valid bids', function () { const res = spec.interpretResponse(serverResponse, []) expect(res).to.be.an('array').that.is.empty @@ -824,6 +1023,181 @@ describe('Taboola Adapter', function () { expect(res).to.deep.equal(expectedRes) }); + it('should interpret display response with PA', function () { + const [bid] = serverResponse.body.seatbid[0].bid; + + const expectedRes = { + 'bids': [ + { + requestId: request.bids[0].bidId, + seatBidId: serverResponse.body.seatbid[0].bid[0].id, + cpm: bid.price, + creativeId: bid.crid, + creative_id: bid.crid, + ttl: 60, + netRevenue: true, + currency: serverResponse.body.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ], + 'fledgeAuctionConfigs': [ + { + 'impId': request.bids[0].bidId, + 'config': { + 'seller': 'pa.taboola.com', + 'resolveToConfig': false, + 'sellerSignals': {}, + 'sellerTimeout': 100, + 'perBuyerSignals': { + 'https://pa.taboola.com': { + 'country': 'US', + 'route': 'AM', + 'cct': [ + 0.02241223, + -0.8686833, + 0.96153843 + ], + 'vct': '-1967600173', + 'ccv': null, + 'ect': [ + -0.13584597, + 2.5825605 + ], + 'ri': '100fb73d4064bc', + 'vcv': '165229814', + 'ecv': [ + -0.39882636, + -0.05216012 + ], + 'publisher': 'test-headerbidding', + 'platform': 'DESK' + } + }, + 'auctionSignals': {}, + 'decisionLogicUrl': 'https://pa.taboola.com/score/decisionLogic.js', + 'interestGroupBuyers': [ + 'https://pa.taboola.com' + ], + 'perBuyerTimeouts': { + '*': 50 + } + } + } + ] + } + + const res = spec.interpretResponse(serverResponseWithPa, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response with partialPA', function () { + const [bid] = serverResponse.body.seatbid[0].bid; + const expectedRes = { + 'bids': [ + { + requestId: request.bids[0].bidId, + seatBidId: serverResponse.body.seatbid[0].bid[0].id, + cpm: bid.price, + creativeId: bid.crid, + creative_id: bid.crid, + ttl: 60, + netRevenue: true, + currency: serverResponse.body.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ], + 'fledgeAuctionConfigs': [ + { + 'impId': request.bids[0].bidId, + 'config': { + 'seller': undefined, + 'resolveToConfig': undefined, + 'sellerSignals': {}, + 'sellerTimeout': undefined, + 'perBuyerSignals': {}, + 'auctionSignals': {}, + 'decisionLogicUrl': undefined, + 'interestGroupBuyers': undefined, + 'perBuyerTimeouts': undefined + } + } + ] + } + + const res = spec.interpretResponse(serverResponseWithPartialPa, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response with wrong PA', function () { + const [bid] = serverResponse.body.seatbid[0].bid; + + const expectedRes = [ + { + requestId: request.bids[0].bidId, + seatBidId: serverResponse.body.seatbid[0].bid[0].id, + cpm: bid.price, + creativeId: bid.crid, + creative_id: bid.crid, + ttl: 60, + netRevenue: true, + currency: serverResponse.body.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ] + + const res = spec.interpretResponse(serverResponseWithWrongPa, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response with empty igbid wrong PA', function () { + const [bid] = serverResponse.body.seatbid[0].bid; + + const expectedRes = [ + { + requestId: request.bids[0].bidId, + seatBidId: serverResponse.body.seatbid[0].bid[0].id, + cpm: bid.price, + creativeId: bid.crid, + creative_id: bid.crid, + ttl: 60, + netRevenue: true, + currency: serverResponse.body.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ] + + const res = spec.interpretResponse(serverResponseWithEmptyIgbidWIthWrongPa, request) + expect(res).to.deep.equal(expectedRes) + }); + it('should set the correct ttl form the response', function () { // set exp-ttl to be 125 const [bid] = serverResponse.body.seatbid[0].bid; diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index f26081b0cef..1e044651315 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/teadsBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; -import { off } from '../../../src/events'; +import * as autoplay from 'libraries/autoplayDetection/autoplay.js' const ENDPOINT = 'https://a.teads.tv/hb/bid-request'; const AD_SCRIPT = '"'; @@ -1059,7 +1059,8 @@ describe('teadsBidAdapter', () => { 'ttl': 360, 'width': 300, 'creativeId': 'er2ee', - 'placementId': 34 + 'placementId': 34, + 'needAutoplay': true }, { 'ad': AD_SCRIPT, 'cpm': 0.5, @@ -1070,6 +1071,7 @@ describe('teadsBidAdapter', () => { 'width': 350, 'creativeId': 'fs3ff', 'placementId': 34, + 'needAutoplay': false, 'dealId': 'ABC_123', 'ext': { 'dsa': { @@ -1132,6 +1134,70 @@ describe('teadsBidAdapter', () => { expect(result).to.eql(expectedResponse); }); + it('should filter bid responses with needAutoplay:true when autoplay is disabled', function() { + let bids = { + 'body': { + 'responses': [{ + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'bidId': '3ede2a3fa0db94', + 'ttl': 360, + 'width': 300, + 'creativeId': 'er2ee', + 'placementId': 34, + 'needAutoplay': true + }, { + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 200, + 'bidId': '4fef3b4gb1ec15', + 'ttl': 360, + 'width': 350, + 'creativeId': 'fs3ff', + 'placementId': 34, + 'needAutoplay': false + }, { + 'ad': AD_SCRIPT, + 'cpm': 0.7, + 'currency': 'USD', + 'height': 600, + 'bidId': 'a987fbc961d', + 'ttl': 12, + 'width': 300, + 'creativeId': 'awuygfd', + 'placementId': 12, + 'needAutoplay': true + }] + } + }; + let expectedResponse = [{ + 'cpm': 0.5, + 'width': 350, + 'height': 200, + 'currency': 'USD', + 'netRevenue': true, + 'meta': { + advertiserDomains: [], + }, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '4fef3b4gb1ec15', + 'creativeId': 'fs3ff', + 'placementId': 34 + } + ] + ; + + const isAutoplayEnabledStub = sinon.stub(autoplay, 'isAutoplayEnabled'); + isAutoplayEnabledStub.returns(false); + let result = spec.interpretResponse(bids); + isAutoplayEnabledStub.restore(); + expect(result).to.eql(expectedResponse); + }); + it('handles nobid responses', function() { let bids = { 'body': { diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 275b9b3bfee..851425574d0 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -736,258 +736,83 @@ describe('triplelift adapter', function () { }); it('should add tdid to the payload if included', function () { - const id = '6bca7f6b-a98a-46c0-be05-6020f7604598'; - bidRequests[0].userId.tdid = id; - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - const payload = request.data; - expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'adserver.org', uids: [{id, ext: {rtiPartner: 'TDID'}}]}]}}); - }); - - it('should add idl_env to the payload if included', function () { - const id = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; - bidRequests[0].userId.idl_env = id; + const tdid = '6bca7f6b-a98a-46c0-be05-6020f7604598'; + bidRequests[0].userIdAsEids = [ + { + source: 'adserver.org', + uids: [ + { + atype: 1, + ext: { + rtiPartner: 'TDID' + }, + id: tdid + } + ] + }, + ]; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'liveramp.com', uids: [{id, ext: {rtiPartner: 'idl'}}]}]}}); + expect(payload.user).to.deep.equal({ext: {eids: [{source: 'adserver.org', uids: [{id: tdid, atype: 1, ext: {rtiPartner: 'TDID'}}]}]}}); }); it('should add criteoId to the payload if included', function () { const id = '53e30ea700424f7bbdd793b02abc5d7'; - bidRequests[0].userId.criteoId = id; - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - const payload = request.data; - expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'criteo.com', uids: [{id, ext: {rtiPartner: 'criteoId'}}]}]}}); - }); - - it('should add adqueryId to the payload if included', function () { - const id = '%7B%22qid%22%3A%229c985f8cc31d9b3c000d%22%7D'; - bidRequests[0].userIdAsEids = [{ source: 'adquery.io', uids: [{ id }] }]; - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - const payload = request.data; - expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'adquery.io', uids: [{id, ext: {rtiPartner: 'adquery.io'}}]}]}}); - }); - - it('should add amxRtbId to the payload if included', function () { - const id = 'Ok9JQkBM-UFlAXEZQ-UUNBQlZOQzgrUFhW-UUNBQkRQTUBPQVpVWVxNXlZUUF9AUFhAUF9PXFY/'; - bidRequests[0].userIdAsEids = [{ source: 'amxdt.net', uids: [{ id }] }]; - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - const payload = request.data; - expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'amxdt.net', uids: [{id, ext: {rtiPartner: 'amxdt.net'}}]}]}}); - }); - - it('should add tdid, idl_env and criteoId to the payload if both are included', function () { - const tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; - const idlEnvId = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; - const criteoId = '53e30ea700424f7bbdd793b02abc5d7'; - bidRequests[0].userId.tdid = tdidId; - bidRequests[0].userId.idl_env = idlEnvId; - bidRequests[0].userId.criteoId = criteoId; - - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - const payload = request.data; - - expect(payload).to.exist; - expect(payload.user).to.deep.equal({ - ext: { - eids: [ - { - source: 'adserver.org', - uids: [ - { - id: tdidId, - ext: { rtiPartner: 'TDID' } - } - ], - }, - { - source: 'liveramp.com', - uids: [ - { - id: idlEnvId, - ext: { rtiPartner: 'idl' } - } - ] - }, + bidRequests[0].userIdAsEids = [ + { + source: 'criteo.com', + uids: [ { - source: 'criteo.com', - uids: [ - { - id: criteoId, - ext: { rtiPartner: 'criteoId' } - } - ] + atype: 1, + ext: { + rtiPartner: 'criteoId' + }, + id: id } ] - } - }); - }); - - it('should consolidate user ids from multiple bid requests', function () { - const tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; - const idlEnvId = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; - const criteoId = '53e30ea700424f7bbdd793b02abc5d7'; - const pubcid = '3261d8ad-435d-481d-abd1-9f1a9ec99f0e'; - - const bidRequestsMultiple = [ - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } + }, ]; - - const request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const payload = request.data; - - expect(payload.user).to.deep.equal({ - ext: { - eids: [ - { - source: 'adserver.org', - uids: [ - { - id: tdidId, - ext: { rtiPartner: 'TDID' } - } - ], - }, - { - source: 'liveramp.com', - uids: [ - { - id: idlEnvId, - ext: { rtiPartner: 'idl' } - } - ] - }, - { - source: 'criteo.com', - uids: [ - { - id: criteoId, - ext: { rtiPartner: 'criteoId' } - } - ] - }, - { - source: 'pubcid.org', - uids: [ - { - id: '3261d8ad-435d-481d-abd1-9f1a9ec99f0e', - ext: { rtiPartner: 'pubcid' } - } - ] - } - ] - } - }); - - expect(payload.user.ext.eids).to.be.an('array'); - expect(payload.user.ext.eids).to.have.lengthOf(4); + expect(payload).to.exist; + expect(payload.user).to.deep.equal({ext: {eids: [{source: 'criteo.com', uids: [{id: id, atype: 1, ext: {rtiPartner: 'criteoId'}}]}]}}); }); - it('should remove malformed ids that would otherwise break call', function () { - let tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; - let idlEnvId = null; // fail; can't be null - let criteoId = '53e30ea700424f7bbdd793b02abc5d7'; - let pubcid = ''; // fail; can't be empty string - - let bidRequestsMultiple = [ - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } - ]; - - let request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); - let payload = request.data; - - expect(payload.user).to.deep.equal({ - ext: { - eids: [ - { - source: 'adserver.org', - uids: [ - { - id: tdidId, - ext: { rtiPartner: 'TDID' } - } - ], - }, + it('should add tdid and criteoId to the payload if both are included', function () { + const tdid = '6bca7f6b-a98a-46c0-be05-6020f7604598'; + const criteoId = '53e30ea700424f7bbdd793b02abc5d7'; + bidRequests[0].userIdAsEids = [ + { + source: 'adserver.org', + uids: [ { - source: 'criteo.com', - uids: [ - { - id: criteoId, - ext: { rtiPartner: 'criteoId' } - } - ] + atype: 1, + ext: { + rtiPartner: 'TDID' + }, + id: tdid } ] - } - }); - - expect(payload.user.ext.eids).to.be.an('array'); - expect(payload.user.ext.eids).to.have.lengthOf(2); - - tdidId = {}; // fail; can't be empty object - idlEnvId = { id: '987654' }; // pass - criteoId = [{ id: '123456' }]; // fail; can't be an array - pubcid = '3261d8ad-435d-481d-abd1-9f1a9ec99f0e'; // pass - - bidRequestsMultiple = [ - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } - ]; - - request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); - payload = request.data; - - expect(payload.user).to.deep.equal({ - ext: { - eids: [ - { - source: 'liveramp.com', - uids: [ - { - id: '987654', - ext: { rtiPartner: 'idl' } - } - ] - }, + }, + { + source: 'criteo.com', + uids: [ { - source: 'pubcid.org', - uids: [ - { - id: pubcid, - ext: { rtiPartner: 'pubcid' } - } - ] + atype: 1, + ext: { + rtiPartner: 'criteoId' + }, + id: criteoId } ] - } - }); - - expect(payload.user.ext.eids).to.be.an('array'); - expect(payload.user.ext.eids).to.have.lengthOf(2); - - tdidId = { id: '987654' }; // pass - idlEnvId = { id: 987654 }; // fail; can't be an int - criteoId = '53e30ea700424f7bbdd793b02abc5d7'; // pass - pubcid = { id: '' }; // fail; can't be an empty string - - bidRequestsMultiple = [ - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, - { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } + }, ]; - request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); - payload = request.data; + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload).to.exist; expect(payload.user).to.deep.equal({ ext: { eids: [ @@ -995,7 +820,8 @@ describe('triplelift adapter', function () { source: 'adserver.org', uids: [ { - id: '987654', + id: tdid, + atype: 1, ext: { rtiPartner: 'TDID' } } ], @@ -1005,6 +831,7 @@ describe('triplelift adapter', function () { uids: [ { id: criteoId, + atype: 1, ext: { rtiPartner: 'criteoId' } } ] diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 18f49f4943e..1e909d79ed4 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -12,6 +12,7 @@ import { setSubmoduleRegistry, syncDelay, } from 'modules/userId/index.js'; +import {UID1_EIDS} from 'libraries/uid1Eids/uid1Eids.js'; import {createEidsArray, EID_CONFIG} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -99,6 +100,25 @@ describe('User ID', function () { } } + function createMockEid(name, source) { + return { + [name]: { + source: source || `${name}Source`, + atype: 3, + getValue: function(data) { + if (data.id) { + return data.id; + } else { + return data; + } + }, + getUidExt: function(data) { + return data.ext + } + } + } + } + function getAdUnitMock(code = 'adUnit-code') { return { code, @@ -644,10 +664,10 @@ describe('User ID', function () { it('pbjs.getUserIdsAsEids should prioritize user ids according to config available to core', () => { init(config); setSubmoduleRegistry([ - createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}), - createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}), - createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}), - createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}) + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}, null, createMockEid('uid2')), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}, null, createMockEid('pubcid')), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}, null, {...createMockEid('uid2'), ...createMockEid('merkleId'), ...createMockEid('lipb')}), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}, null, createMockEid('merkleId')) ]); config.setConfig({ userSync: { @@ -679,6 +699,38 @@ describe('User ID', function () { }); }); + it('pbjs.getUserIdsAsEids should prioritize the uid1 according to config available to core', () => { + init(config); + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {tdid: {id: 'uid1_value'}}}, null, UID1_EIDS), + createMockIdSubmodule('mockId2Module', {id: {tdid: {id: 'uid1Id_value_from_mockId2Module'}}}, null, UID1_EIDS), + createMockIdSubmodule('mockId3Module', {id: {tdid: {id: 'uid1Id_value_from_mockId3Module'}}}, null, UID1_EIDS) + ]); + config.setConfig({ + userSync: { + idPriority: { + tdid: ['mockId2Module', 'mockId3Module', 'mockId1Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' } + ] + } + }); + + const ids = { + 'tdid': { id: 'uid1Id_value_from_mockId2Module' }, + }; + + return getGlobal().getUserIdsAsync().then(() => { + const eids = getGlobal().getUserIdsAsEids(); + const expected = createEidsArray(ids); + expect(eids).to.deep.equal(expected); + }) + }); + describe('EID updateConfig', () => { function mockSubmod(name, eids) { return createMockIdSubmodule(name, null, null, eids); @@ -3554,11 +3606,16 @@ describe('User ID', function () { it('pbjs.getUserIdsAsEidBySource with priority config available to core', () => { init(config); + const uid2Eids = createMockEid('uid2', 'uidapi.com') + const pubcEids = createMockEid('pubcid', 'pubcid.org') + const liveIntentEids = createMockEid('lipb', 'liveintent.com') + const merkleEids = createMockEid('merkleId', 'merkleinc.com') + setSubmoduleRegistry([ - createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}), - createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}), - createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}), - createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}) + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}, null, uid2Eids), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}, null, {...pubcEids, ...liveIntentEids}), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}, null, {...uid2Eids, ...pubcEids, ...liveIntentEids}), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}, null, merkleEids) ]); config.setConfig({ userSync: { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index bc5165c8d54..5515002a054 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -19,6 +19,7 @@ import {version} from 'package.json'; import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; +import {deepSetValue} from 'src/utils.js'; export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -108,7 +109,19 @@ const BIDDER_REQUEST = { 'ortb2': { 'site': { 'cat': ['IAB2'], - 'pagecat': ['IAB2-2'] + 'pagecat': ['IAB2-2'], + 'content': { + 'data': [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }] + } }, 'regs': { 'gpp': 'gpp_string', @@ -131,6 +144,15 @@ const BIDDER_REQUEST = { 'bitness': '64', 'architecture': '' } + }, + user: { + data: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], } }, }; @@ -318,6 +340,23 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + contentData: [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }], + userData: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, @@ -340,7 +379,8 @@ describe('VidazooBidAdapter', function () { } } } - }); + }) + ; }); it('should build banner request for each size', function () { @@ -405,6 +445,23 @@ describe('VidazooBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentData: [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }], + userData: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], webSessionId: webSessionId } }); @@ -478,6 +535,23 @@ describe('VidazooBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentData: [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }], + userData: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], webSessionId: webSessionId }; @@ -523,6 +597,15 @@ describe('VidazooBidAdapter', function () { expect(requests).to.have.length(2); }); + it('should set fledge correctly if enabled', function () { + config.resetConfig(); + const bidderRequest = utils.deepClone(BIDDER_REQUEST); + bidderRequest.fledgeEnabled = true; + deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); + const requests = adapter.buildRequests([BID], bidderRequest); + expect(requests[0].data.fledge).to.equal(1); + }); + after(function () { $$PREBID_GLOBAL$$.bidderSettings = {}; config.resetConfig(); diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index 7de8474d7c9..d562d9ffd13 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -48,6 +48,120 @@ describe('weboramaRtdProvider', function() { }; expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); }); + + it('instantiate with empty sfbxLiteData should return true', function() { + const moduleConfig = { + params: { + sfbxLiteDataConf: {}, + } + }; + expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); + }); + + describe('webo user data should check gdpr consent', function() { + it('should initialize if gdpr does not applies', function() { + const moduleConfig = { + params: { + weboUserDataConf: {} + } + }; + const userConsent = { + gdpr: { + gdprApplies: false, + }, + } + expect(weboramaSubmodule.init(moduleConfig, userConsent)).to.equal(true); + }); + it('should initialize if gdpr applies and consent is ok', function() { + const moduleConfig = { + params: { + weboUserDataConf: {} + } + }; + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + 5: true, + 6: true, + 9: true, + }, + }, + specialFeatureOptins: { + 1: true, + }, + vendor: { + consents: { + 284: true, + }, + } + }, + }, + } + expect(weboramaSubmodule.init(moduleConfig, userConsent)).to.equal(true); + }); + it('should NOT initialize if gdpr applies and consent is nok: miss consent vendor id', function() { + const moduleConfig = { + params: { + weboUserDataConf: {} + } + }; + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + }, + specialFeatureOptins: {}, + vendor: { + consents: { + 284: false, + }, + } + }, + }, + } + expect(weboramaSubmodule.init(moduleConfig, userConsent)).to.equal(false); + }); + it('should NOT initialize if gdpr applies and consent is nok: miss one purpose id', function() { + const moduleConfig = { + params: { + weboUserDataConf: {} + } + }; + const userConsent = { + gdpr: { + gdprApplies: true, + vendorData: { + purpose: { + consents: { + 1: false, + 3: true, + 4: true, + }, + }, + specialFeatureOptins: {}, + vendor: { + consents: { + 284: true, + }, + } + }, + }, + } + expect(weboramaSubmodule.init(moduleConfig, userConsent)).to.equal(false); + }); + }); }); describe('Handle Set Targeting and Bid Request', function() { diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 68cf3459c5f..3c842c3a308 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -512,20 +512,6 @@ describe('YieldmoAdapter', function () { expect(buildVideoBidAndGetVideoParam().mimes).to.deep.equal(['video/mkv']); }); - it('should validate protocol in video bid request', function () { - expect( - spec.isBidRequestValid( - mockVideoBid({}, {}, { protocols: [2, 3, 11] }) - ) - ).to.be.true; - - expect( - spec.isBidRequestValid( - mockVideoBid({}, {}, { protocols: [2, 3, 10] }) - ) - ).to.be.false; - }); - describe('video.skip state check', () => { it('should not set video.skip if neither *.video.skip nor *.video.skippable is present', function () { utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = false; diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 81617b93d3c..f9cfe2dde6a 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -59,7 +59,7 @@ describe('Zeta Ssp Bid Adapter', function () { sid: 'publisherId', tagid: 'test_tag_id', site: { - page: 'testPage' + page: 'http://www.zetaglobal.com/page?param=value' }, app: { bundle: 'testBundle' @@ -209,6 +209,7 @@ describe('Zeta Ssp Bid Adapter', function () { id: '12345', seatbid: [ { + seat: '1', bid: [ { id: 'auctionId', @@ -234,7 +235,7 @@ describe('Zeta Ssp Bid Adapter', function () { id: '123', site: { id: 'SITE_ID', - page: 'page.com', + page: 'http://www.zetaglobal.com/page?param=value', domain: 'domain.com' }, user: { @@ -262,7 +263,7 @@ describe('Zeta Ssp Bid Adapter', function () { id: '123', site: { id: 'SITE_ID', - page: 'page.com', + page: 'http://www.zetaglobal.com/page?param=value', domain: 'domain.com' }, user: { @@ -311,7 +312,7 @@ describe('Zeta Ssp Bid Adapter', function () { it('Test page and domain in site', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); - expect(payload.site.page).to.eql('http://www.zetaglobal.com/page?param=value'); + expect(payload.site.page).to.eql('zetaglobal.com/page'); expect(payload.site.domain).to.eql('zetaglobal.com'); }); @@ -601,6 +602,7 @@ describe('Zeta Ssp Bid Adapter', function () { expect(bidResponse[0].mediaType).to.eql(BANNER); expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); expect(bidResponse[0].vastXml).to.be.undefined; + expect(bidResponse[0].dspId).to.eql(zetaResponse.body.seatbid[0].seat); }); it('Test the response default mediaType:video', function () { @@ -610,6 +612,7 @@ describe('Zeta Ssp Bid Adapter', function () { expect(bidResponse[0].mediaType).to.eql(VIDEO); expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); expect(bidResponse[0].vastXml).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + expect(bidResponse[0].dspId).to.eql(zetaResponse.body.seatbid[0].seat); }); it('Test the response mediaType:video from ext param', function () { @@ -624,6 +627,7 @@ describe('Zeta Ssp Bid Adapter', function () { expect(bidResponse[0].mediaType).to.eql(VIDEO); expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); expect(bidResponse[0].vastXml).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + expect(bidResponse[0].dspId).to.eql(zetaResponse.body.seatbid[0].seat); }); it('Test the response mediaType:banner from ext param', function () { @@ -638,6 +642,7 @@ describe('Zeta Ssp Bid Adapter', function () { expect(bidResponse[0].mediaType).to.eql(BANNER); expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); expect(bidResponse[0].vastXml).to.be.undefined; + expect(bidResponse[0].dspId).to.eql(zetaResponse.body.seatbid[0].seat); }); it('Test provide segments into the request', function () {